[10.11] Ce este fiasco-ul de inițializare statică?
O modalitate neclară și insidioasă de a vă ucide proiectul.
O eroare în ordinea inițializării statice este un aspect foarte delicat și adesea incorect perceput de C ++. Din păcate, această eroare este foarte greu de capturat, deoarece apare înainte de a intra în funcția principal ().
Imaginați-vă că aveți două obiecte statice, x și y. care se află în două fișiere sursă diferite, de exemplu x.cpp și y.cpp. Și calea constructorului obiectului y numește orice metodă a obiectului x.
Asta e tot. Atât de simplu.
Problema este că aveți exact șanse de cincizeci de procente de dezastru. Dacă se întâmplă ca prima unitate de traducere cu x.cpp să fie inițializată, atunci totul este bine. Dacă prima unitate de traducere a fișierului y.cpp este inițializată, atunci constructorul de obiect y va fi pornit înaintea constructorului x. și tu acoperă. Ie constructorul y va numi metoda obiectului x. când x în sine nu este încă creată.
Du-te la McDonald's. Fa Big Mac-urile, uitați de cursuri.
Notă: erorile de inițializare statică nu se aplică tipurilor de bază / încorporate, cum ar fi int sau char *. De exemplu, dacă creați o variabilă statică de tip float. nu veți avea probleme cu ordinea de inițializare. Problema apare numai atunci când obiectul dvs. static sau global are un constructor.
[10.12] Cum se poate preveni o eroare în ordinea inițializării statice?
Utilizați "creați la prima utilizare", adică puneți obiectul static în funcție.
Imaginați-vă că avem două clase de Fred și Barney. Există un obiect global de tip Fred. cu numele x. și un obiect global de tip Barney, cu numele y. Constructorul Barney apelează metoda goBowling () a obiectului x. Fișierul x.cpp conține definiția obiectului x.
Fișierul y.cpp conține definiția obiectului y.
Pentru completare, să presupunem că constructorul Barney :: Barney () arată astfel:
După cum am descris mai sus [10.11], problema apare dacă y este creat mai devreme decât x. care se întâmplă în 50% din cazuri, deoarece x și y sunt în diferite fișiere sursă.
Există multe soluții pentru această problemă, însă unul foarte simplu și portabil este înlocuirea obiectului global Fredx. funcția globală x (). care returnează un obiect de tip Fred prin referință.
Aceasta se numește "crearea atunci când utilizați pentru prima dată", obiectul global Fred este creat când îl accesați pentru prima dată.
Aspectul negativ al acestei tehnici este faptul că obiectul Fred nu este distrus nicăieri. Book C ++ FAQ Book descrie o tehnică suplimentară care vă permite să rezolvați această problemă (adevărat, cu costul apariției posibilelor erori ale ordinii de dezinfectare statică).
Notă: erorile de inițializare statică nu se aplică tipurilor de bază / încorporate, cum ar fi int sau char *. De exemplu, dacă creați o variabilă statică de tip float. nu veți avea probleme cu ordinea de inițializare. Problema apare numai atunci când obiectul dvs. static sau global are un constructor.
[10.13] Cum să ne ocupăm de erorile ordinii inițializării statice a obiectelor - membrii clasei?
Utilizați aceeași tehnică descrisă în [10.12], dar în locul unei funcții globale, utilizați o funcție de memorie statică.
Să presupunem că aveți clasa X. în care există un obiect static Fred.
Firește, acest membru static este inițializat separat:
Din nou, firește, obiectul Fred va fi folosit în una sau mai multe metode de clasa X.
Problema va apărea dacă cineva undeva într-un fel numește această metodă înainte ca obiectul Fred să fie creat. De exemplu, dacă cineva creează un obiect static X și îl numește someMethod () în timpul inițializării statice, atunci destinul tău este în întregime în mâinile compilatorului, care fie creează X :: x_. înainte de a fi apelat un Metod (). sau numai după.
(Ar trebui să menționez că comitetul ANSI / ISO C ++ lucrează la această problemă, dar compilatorii care lucrează în funcție de cele mai recente modificări nu sunt încă disponibili, eventual în viitor, adăugările vor fi făcute în această secțiune datorită situației modificate.)
În orice caz, puteți păstra întotdeauna portabilitatea (și aceasta este o metodă absolut sigură) prin înlocuirea elementului static X :: x_ cu o funcție statică:
Firește, acest membru static este inițializat separat:
După aceasta, pur și simplu schimbați toate x_ la x ().
Notă: erorile de inițializare statică nu se aplică tipurilor de bază / încorporate, cum ar fi int sau char *. De exemplu, dacă creați o variabilă statică de tip float. nu veți avea probleme cu ordinea de inițializare. Problema apare numai atunci când obiectul dvs. static sau global are un constructor.
[10.14] Cum rezolv eroarea care a avut loc în constructor?
Generează o excepție. Consultați detaliile din [17.1].
Secțiunea [11]: Distrugătoare
[11.1] Ce este un distrugător?
Distrugatorul este executarea ultimei voințe a obiectului.
Destructorii sunt utilizați pentru a elibera resursele ocupate de obiect. De exemplu, clasa de blocare poate bloca o resursă pentru utilizarea exclusivă, iar distrugătorul acesteia poate elibera această resursă. Dar cel mai obișnuit caz este atunci când este folosit nou în constructor. și în destructor - ștergeți.
Destructorul este funcția "pregătiți pentru moarte". Adesea distrugatorul de cuvinte este scurtat la dtor.
[11.2] În ce ordine sunt numiți distrugătorii obiectelor locale?
În ordine inversă față de ceea ce au fost create aceste obiecte: create pentru prima dată - ultima va fi distrusă.
În exemplul următor, destructorul pentru obiectul b va fi numit mai întâi și numai atunci distrugătorul pentru obiectul a:
[11.3] În ce ordine sunt numiți distrugătorii obiectelor de matrice?
În ordinea inversă a creației: prima creată - ultima va fi distrusă.
În următorul exemplu, ordinea de a apela destructorii este următoarea: a [9], a [8]. a [1], a [0]:
[11.4] Pot reîncărca distrugătorul pentru clasa mea?
Fiecare clasă poate avea doar un singur distrugător. Pentru clasa Fred, ea va fi întotdeauna numită Fred ::
Fred (). Distrugătorul nu este niciodată trecut prin parametri, iar distrugătorul nu întoarce niciodată nimic.
Totuși, nu ați putut specifica parametrii pentru distrugător, deoarece nu ați apelat direct la destructor [11.5] (mai precis, aproape niciodată [11.10]).
[11.5] Pot numi în mod explicit un destructor pentru o variabilă locală?
Distructorul va mai fi chemat din nou când se va ajunge la bara de închidere> sfârșitul blocului în care a fost creată variabila locală. Acest apel este garantat de limbă și se întâmplă automat; nu există nici o modalitate de a împiedica acest apel. Dar consecințele repetării destructorului pentru același obiect pot fi deplorabile. Bah! Și tu ești mort.
[11.6] Ce se întâmplă dacă vreau ca o variabilă locală să "moară" înainte de a închide brațul? Pot provoca un destructor pentru o variabilă locală dacă este absolut necesar?
Nu! [A se vedea răspunsul la întrebarea anterioară [11.5]].
Să presupunem că efectul secundar (dorit) de a apela destructorul pentru un obiect Fișier local este de a închide fișierul. Și să presupunem că avem o instanță a clasei f class și dorim ca fișierul f să fie închis înainte de sfârșitul scopului său (adică înainte de):
Pentru această problemă, există o soluție simplă, pe care o prezentăm în [11.7]. Dar pentru moment, trebuie doar să vă amintiți următoarele: nu puteți numi în mod explicit distrugătorul [11.5].
[11.7] Ok, nu voi numi în mod explicit distrugătorul. Dar cum pot face față acestei probleme?
[Vezi și răspunsul la întrebarea anterioară [11.6]].
Doar puneți variabila locală într-un bloc separat. Durata de viață corespunzătoare a acestei variabile este:
[11.8] Ce se întâmplă dacă nu pot pune o variabilă într-un bloc separat?
În cele mai multe cazuri, puteți utiliza un bloc suplimentar pentru a limita durata de viață a variabilei dvs. [11.7]. Dar dacă dintr-un motiv nu puteți adăuga un bloc, adăugați o funcție membră care va efectua aceleași acțiuni ca și distrugătorul. Dar amintiți-vă: nu vă puteți numi singur pe distrugător!
De exemplu, în cazul clasei File. puteți adăuga metoda close (). Un distrugător normal va apela aproape (). Rețineți că metoda close () va marca un obiect File. astfel încât apelurile ulterioare să nu încerce să închidă fișierul deja închis. De exemplu, puteți seta variabila membru fileHandle_ la o valoare neutilizată, cum ar fi -1, și verificați la început dacă fileHandle_ nu are o valoare de -1.
Rețineți că și alte metode ale clasei File ar putea să trebuiască să verifice dacă fileHandle_ este setat la -1 (adică dacă fișierul este închis).
De asemenea, rețineți că toți constructorii care nu deschid fișierul trebuie să seteze fileHandle_ la -1.
[11.9] Pot numi în mod explicit un destructor pentru un obiect creat cu un nou?
Cel mai probabil nu.
Cu excepția cazului în care utilizați sintaxa de alocare pentru noul operator [11.10], trebuie doar să ștergeți obiectele utilizând ștergerea. mai degrabă decât provocând un distrugător explicit. Să presupunem că ați creat un obiect cu noul obișnuit:
În acest caz, distrugătorul Fred ::
Fred () va fi apelat automat atunci când ștergeți un obiect:
Nu trebuie să numiți în mod explicit distrugătorul, pentru că prin aceasta nu eliberați memoria alocată obiectului Fred. Amintiți-vă: ștergeți p face două lucruri simultan [16.8]: sună distrugătorul și eliberează memoria.
[11.10] Care este sintaxa "destinație nouă de plasare" și de ce este necesară?
Există multe cazuri pentru utilizarea sintaxei de alocare pentru noi. Cel mai simplu este să puteți utiliza sintaxa de plasare pentru a plasa un obiect într-o anumită locație din memorie. Pentru a face acest lucru, specificați locația prin transmiterea unui indicator la acesta în noul operator.
Linia # 1 este creat de un șir de octeți sizeof (Fred), a cărui mărime este suficientă pentru stocarea Fred obiect. În linia # 2, se creează pointerul locului. care indică primul octet matrice (programatori cu experiență C poate fi observat că nu puteți crea acest indice, am făcut-o doar pentru a păstra codul mai ușor de înțeles [Ca în cazul în care - :) YM]). În linia # 3, de fapt, se apelează doar constructorul Fred :: Fred (). Acest lucru în constructorul Fred va fi egal cu pointerul locului. Astfel, pointerul returnat va fi, de asemenea, egal cu locul.
PERICOL: Folosind sintaxa nouă destinație de plasare pe care își asume întreaga responsabilitate pentru faptul că indicatorul ai trecut indică suficientă pentru a stoca zona de memorie obiect cu alinierea (aliniere), pe care o doriți pentru dvs. obiect. Nici compilatorul, nici biblioteca nu vor verifica corectitudinea acțiunilor dvs. în acest caz. În cazul în care clasa Fred trebuie să fie aliniate la limita de patru octeți, dar ți-a dat în noul index pe o locație de memorie non-aliniate, ați putea fi în mare încurcătură (dacă nu știi ce „aliniere“ (aliniere), vă rugăm să nu folosiți sintaxa de plasare nouă ). Te-am avertizat.
De asemenea, sunteți responsabil pentru distrugerea obiectului plasat. Pentru a face acest lucru, trebuie să-l numiți în mod explicit pe distrugător:
Acesta este aproape singurul caz în care trebuie să numiți în mod explicit distrugătorul.
[11.11] Când scriu un destructor, ar trebui să numesc în mod explicit distrugătorii pentru obiectele membre ale clasei mele?
Nu, nu este. Niciodată nu trebuie să apelați explicit un destructor (cu excepția cazului cu noua sintaxă [11.10]).
[11.12] Când scriu un distrugător al unei clase derivate, trebuie să-l numesc în mod explicit pe distrugătorul strămoșului?
Nu, nu este. Niciodată nu trebuie să numiți în mod explicit distrugătorul (cu excepția cazului cu noua sintaxă [11.10]).
Un destructor al unei clase derivate (implicit, creat de compilator sau explicit descris de dvs.) îl sună automat pe distrugătorii strămoșilor. Stramosii sunt distruși după distrugerea obiectelor - membrii clasei derivate. În cazul moștenirii multiple, strămoșii imediați ai clasei sunt distruși în ordinea inversă a apariției lor în lista moștenirii.
Notă: în cazul moștenirii virtuale, ordinea de distrugere a claselor este mai complicată. Dacă vă bazați pe ordinea distrugerii clasei în cazul moștenirii virtuale, veți avea nevoie de mai multe informații decât conținutul FAQ.