Pentru a cita Standardul limbajului C ++, "Durata de stocare este proprietatea unui obiect care definește durata minimă de viață potențială a spațiului de stocare care conține obiectul". În esență, este ceea ce vă spune cât timp trebuie să vă așteptați ca o variabilă să fie utilizabilă. Variabila ar putea fi un tip fundamental, cum ar fi un int sau un tip complex, cum ar fi o clasă. Indiferent de tipul său, o variabilă este garantată doar pentru o perioadă atâta timp cât limbajul de programare spune că ar trebui.
C ++ gestionează memoria foarte diferit de C #. Pentru un singur lucru, nu există cerința de a avea un colector de gunoi, iar câteva implementări oferă unul. În măsura în care implementările C ++ au gestionarea automată a memoriei, acestea se realizează în cea mai mare parte prin indicatori inteligenți și prin numărarea referințelor. Clasele C ++ nu locuiesc automat pe o grămadă (administrate de GC sau altfel). În schimb, ele funcționează mult mai mult ca structurile din C #.
Puteți împinge o instanță de clasă C ++ pe o grămadă atunci când este nevoie, dar dacă o declarați local și nu faceți nimic amuzant, atunci va avea o durată automată, de obicei implementată folosind un stiva și va fi distrusă automat când programul părăsește domeniul în care există clasa.
C ++ vă oferă mai mult control asupra gestionării memoriei decât C #. O consecință a acestui lucru este că limbajul C ++ și mediul de execuție nu pot face atât de mult pentru a preveni codul eronat ca limbajul C # și CLR. Una dintre cheile de a fi un bun programator C ++ este de a intelege cum functioneaza gestionarea memoriei si de a folosi cele mai bune practici pentru a scrie un cod eficient si corect.
Variabilele globale, inclusiv cele din interiorul spațiilor de nume și variabilele marcate cu cuvântul cheie cu durată de viață statică au o durată de stocare statică.
Variabilele globale sunt inițializate în timpul inițializării programelor (adică perioada înainte ca programul să înceapă de fapt executarea funcției principale sau a funcției wmain). Acestea sunt inițializate în ordinea în care sunt definite în codul sursă. În general, nu este o idee bună să vă bazați pe ordinea de inițializare deoarece refactorizarea și alte modificări aparent nevinovate ar putea introduce cu ușurință un potențial bug în programul dvs..
Statica locală este inițializată zero, prima execuție a programului ajunge la blocul care conține statica locală. În mod obișnuit, ele vor fi inițializate la valorile specificate sau inițializate apelând constructorul specificat la acel punct. Cesiunea de atribuire a valorii sau faza de construcție nu este necesară până când programul ajunge și execută instrucțiunea, cu excepția unor circumstanțe foarte rare. Odată inițializată o statică locală, inițializarea specificată cu declarația sa nu va mai reîncepe niciodată. Aceasta, desigur, este exact ceea ce ne-am aștepta de la o statică locală. Dacă se menține inițializându-se de fiecare dată când programul a ajuns la linia de definiție, atunci ar fi același lucru ca un local non-static.
Puteți atribui alte valori la statica globală și locală, cu excepția cazului în care, de asemenea, le faceți const, desigur.
Într-un bloc, un obiect are o durată automată dacă este definit fără ca noul operator să-l instanțieze și fără un cuvânt cheie cu durata de stocare, deși poate avea în mod opțional cuvântul cheie înregistrat. Aceasta înseamnă că obiectul este creat în punctul în care este definit și este distrus atunci când programul iese din blocul în care a fost declarată variabila sau când este atribuită o nouă valoare variabilei sale.
Notă: Cuvântul cheie auto a fost o modalitate de selectare în mod explicit a duratei de stocare automată. În C ++ 11, această utilizare a fost eliminată. Acum este echivalentul cuvântului cheie var în C #. Dacă încercați să compilați ceva folosind vechea semnificație a funcției auto, veți primi o eroare de compilator deoarece auto ca un specificator de tip trebuie să fie singurul specificator de tip.
Durata dinamică este rezultatul utilizării fie a operatorului nou, fie a operatorului nou []. Noul operator este folosit pentru a aloca obiecte unice, în timp ce noul operator [] este utilizat pentru alocarea de rețele dinamice. Trebuie să țineți evidența dimensiunii unui matrice alocată dinamic. În timp ce implementarea C ++ va elibera în mod corespunzător o matrice alocată dinamic, cu condiția să utilizați operatorul de ștergere [], nu există un mod ușor sau portabil de determinare a mărimii acelei alocări. Probabil va fi imposibil. Obiectele unice sunt eliberate cu operatorul de ștergere.
Când alocați memoria utilizând noul sau noul [], valoarea returnată este un indicator. Un pointer este o variabilă care deține o adresă de memorie. În C #, dacă setați toate referințele la un obiect la valoarea nulă sau la altă valoare, atunci memoria nu mai este accesibilă în programul dvs., astfel încât GC poate elibera acea memorie pentru alte utilizări.
În C ++, dacă setați toate indicii la un obiect la nullptr sau la altă valoare și nu puteți afla adresa inițială utilizând aritmetica pointerului, atunci v-ați pierdut capacitatea de a elibera acea memorie folosind operatorii de ștergere sau ștergere [] . Ați creat astfel o scurgere de memorie. Dacă un program scapă de memorie suficientă, eventual se va prăbuși, deoarece sistemul va rula fără adrese de memorie pentru el. Chiar și înainte de aceasta, calculatorul va încetini oribil, deoarece este forțat să mărească paginarea pentru a se adapta amprentei de memorie din ce în ce mai mare a programului dvs. (presupunând că are memorie virtuală, care lipsește de la cele mai multe telefoane inteligente).
Notă: Un pointer const, cum ar fi someStr în instrucțiune const wchar_t * someStr = L "Bună ziua!";
nu este un indicator de durată dinamic. Această memorie este doar o parte a programului în sine. Dacă încercați să apelați ștergerea sau ștergerea [] pe ea, programul se va prăbuși. Un șir este o serie de caractere, însă, dacă ar fi bine să îl ștergeți, atunci operatorul de ștergere [] ar fi cel corect de folosit.
Când se ocupă de memorie dinamică, pentru a elimina eventualele scurgeri și pentru a limita posibilitatea altor bug-uri asemănătoare, trebuie să utilizați întotdeauna un pointer inteligent, cum ar fi std :: unique_ptr
sau std :: shared_ptr
. Vom discuta despre acestea în capitolul care acoperă indicii.
Durata firului este cea mai puțin frecvent utilizată durată de stocare. Ea a fost doar recent standardizată. Începând cu această scriere, câțiva distribuitori de compilatoare, dacă este cazul, au implementat suport pentru noul cuvânt cheie thread_local din standardul C ++ 11.
Acest lucru este sigur să se schimbe, dar pentru moment puteți utiliza extensii specifice furnizorilor, cum ar fi extensia specifică Microsoft (thread) sau extensia specifică GCC __thread dacă aveți nevoie de funcționalități de acest tip.
Durata threadului este similară cu durata statică, cu excepția faptului că în loc să dureze durata de viață a programului, aceste variabile sunt locale pentru fiecare fir; copia firului există pentru durata firului. Instanța fiecărui fir a unui obiect durată a firului este inițializată atunci când este utilizată pentru prima dată în fir și este distrusă atunci când firul iese. Un obiect durată a firului nu își moșteneste valoarea din firul care a inițiat firul în care există.
Durata de stocare automată este, de obicei, forma corectă a duratei de stocare a obiectelor, cu excepția cazului în care aveți nevoie de ele pentru a supraviețui domeniului în care au fost create. Dacă este cazul, ar trebui să alegeți una dintre duratele de stocare rămase care se potrivesc cel mai bine necesităților dvs..
Puteți să vă abateți de la aceste recomandări, dacă acest lucru are sens, dar în majoritatea cazurilor acest ghid vă va îndruma corect.
Următorul eșantion este inclus pentru a ajuta la clarificarea acestor concepte. Eșantionul este foarte documentat, deci nu este inclus nici un comentariu suplimentar. Vă încurajez cu tărie să construiți și să rulați acest eșantion particular. Văzând ieșirea în timp ce treceți prin cod vă va ajuta să înțelegeți mai ușor aceste concepte decât să citiți codul.
Exemplu: StorageDurationSample \ SomeClass.h
#pragma o dată #include#include clasa SomeClass public: explicit SomeClass (valoarea int = 0); SomeClass (valoarea int, const wchar_t * stringId); ~ SomeClass (void); int GetValue (void) return m_value; void SetValue (valoare int) m_value = value; static std :: unique_ptr s_someClass; private: int m_value; std :: wstring m_stringId; ;
Exemplu: StorageDurationSample \ SomeClass.cpp
# include "SomeClass.h" #include#include #include #include #include #include #include folosind namespace std; SomeClass :: SomeClass (valoare int): m_value (value), m_stringId (L "(Nu este furnizat codul de șir.)) SomeClass * localThis = this; auto addr = reinterpret_cast (localThis); wcout << L"Creating SomeClass instance." << endl << L"StringId: " << m_stringId.c_str() << L"." << endl << L"Address is: '0x" << setw(8) << setfill(L'0') << hex << addr << dec << L"'." << endl << L"Value is '" << m_value << L"'." << endl << L"Thread id: '" << this_thread::get_id() << L"'." << endl << endl; SomeClass::SomeClass( int value, const wchar_t* stringId ) : m_value(value), m_stringId(stringId) SomeClass* localThis = this; auto addr = reinterpret_cast (localThis); wcout << L"Creating SomeClass instance." << endl << L"StringId: " << m_stringId.c_str() << L"." << endl << L"Address is: '0x" << setw(8) << setfill(L'0') << hex << addr << dec << L"'." << endl << L"Value is '" << m_value << L"'." << endl << L"Thread id: '" << this_thread::get_id() << L"'." << endl << endl; SomeClass::~SomeClass(void) // This is just here to clarify that we aren't deleting a // new object when we replace an old object with it, despite // the order in which the Creating and Destroying output is // shown. m_value = 0; SomeClass* localThis = this; auto addr = reinterpret_cast (localThis); wcout << L"Destroying SomeClass instance." << endl << L"StringId: " << m_stringId.c_str() << L"." << endl << L"Address is: '0x" << setw(8) << setfill(L'0') << hex << addr << dec << L"'." << endl << L"Thread id: '" << this_thread::get_id() << L"'." << endl << endl; // Note that when creating a static member variable, the definition also // needs to have the type specified. Here, we start off with // 'unique_ptr "înainte de a trece la // 'SomeClass :: s_someClass = ...;' atribuirea valorii. unique_ptr SomeClass :: s_someClass = unique_ptr (noul SomeClass (10, L "s_someClass"));
Exemplu: StorageDurationSample \ StorageDurationSample.cpp
#include#include #include #include #include #include #include "SomeClass.h" #include "... /pchar.h" folosind namespace std; Structură SomeStruct int Value; ; namespace Value // Visual C ++ nu suportă thread_local din VS 2012 RC. Putem // mimeze partial thread_local cu _declspec (thread), dar nu putem // sa avem lucruri ca clase cu functii (inclusiv constructori si destructori) cu _declspec (thread). _declspec (fir) SomeStruct ThreadLocalSomeStruct = ; // g_staticSomeClass are o durată statică. Există până când programul // se termină sau până când îi este atribuită o altă valoare. Chiar dacă ați lăsat // off cuvântul cheie static, în acest caz ar fi încă static deoarece // nu este o variabilă locală, nu este dinamic și nu este o variabilă locală / thread. statică SomeClass g_staticSomeClass = SomeClass (20, L "g_staticSomeClass"); // Această metodă creează o instanță SomeClass și apoi modifică // valoarea. void ChangeAndPrintValue (valoarea int) // Crearea unui șir de identificatori. wstringstream wsStr (L ""); wsStr << L"ChangeAndPrintValue thread id: '" << this_thread::get_id() << L"'"; // Create a SomeClass instance to demonstrate function-level block scope. SomeClass sc(value, wsStr.str().c_str()); // Demonstrate _declspec(thread). wcout << L"Old value is " << Value::ThreadLocalSomeStruct.Value << L". Thread id: '" << this_thread::get_id() << L"'." << endl; Value::ThreadLocalSomeStruct.Value = value; wcout << L"New value is " << Value::ThreadLocalSomeStruct.Value << L". Thread id: '" << this_thread::get_id() << L"'." << endl; void LocalStatic(int value) static SomeClass sc(value, L"LocalStatic sc"); //// If you wanted to reinitialize sc every time, you would have to //// un-comment the following line. This, however, would defeat the //// purpose of having a local static. You could do something //// similar if you wanted to reinitialize it in certain circumstances //// since that would justify having a local static. //sc = SomeClass(value, L"LocalStatic reinitialize"); wcout << L"Local Static sc value: '" << sc.GetValue() << L"'." << endl << endl; int _pmain(int /*argc*/, _pchar* /*argv*/[]) // Automatic storage; destroyed when this function ends. SomeClass sc1(1, L"_pmain sc1"); wcout << L"sc1 value: '" << sc1.GetValue() << L"'." << endl << endl; // The braces here create a new block. This means that // sc2 only survives until the matching closing brace, since // it also has automatic storage. SomeClass sc2(2, L"_pmain sc2"); wcout << L"sc2 value: '" << sc2.GetValue() << L"'." << endl << endl; LocalStatic(1000); // Note: The local static in LocalStatic will not be reinitialized // with 5000. See the function definition for more info. LocalStatic(5000); // To demonstrate _declspec(thread) we change the value of this // thread's Value::ThreadLocalSomeStruct to 20 from its default 0. ChangeAndPrintValue(20); // We now create a new thread that automatically starts and // changes the value of Value::ThreadLocalSomeStruct to 40. If it // were shared between threads, then it would be 20 from the // previous call to ChangeAndPrintValue. But it's not. Instead, it // is the default 0 that we would expect as a result of this being // a new thread. auto thr = thread(ChangeAndPrintValue, 40); // Wait for the thread we just created to finish executing. Note that // calling join from a UI thread is a bad idea since it blocks // the current thread from running until the thread we are calling // join on completes. For WinRT programming, you want to make use // of the PPLTasks API instead. thr.join(); // Dynamic storage. WARNING: This is a 'naked' pointer, which is a very // bad practice. It is here to clarify dynamic storage and to serve // as an example. Normally, you should use either // std::unique_ptr or std::shared_ptr to wrap any memory allocated with // the 'new' keyword or the 'new[]' keyword. SomeClass* p_dsc = new SomeClass(1000, L"_pmain p_dsc"); const std::size_t arrIntSize = 5; // Dynamic storage array. THE SAME WARNING APPLIES. int* p_arrInt = new int[arrIntSize]; // Note that there's no way to find how many elements arrInt // has other than to manually track it. Also note that the values in // arrInt are not initialized (i.e. it's not arrIntSize zeroes, it's // arrIntSize arbitrary integer values). for (int i = 0; i < arrIntSize; i++) wcout << L"i: '" << i << L"'. p_arrInt[i] = '" << p_arrInt[i] << L"'." << endl; // Assign a value of i to this index. p_arrInt[i] = i; wcout << endl; //// If you wanted to zero out your dynamic array, you could do this: //uninitialized_fill_n(p_arrInt, arrIntSize, 0); for (int i = 0; i < arrIntSize; i++) wcout << L"i: '" << i << L"'. p_arrInt[i] = '" << p_arrInt[i] << L"'." << endl; // If you forgot this, you would have a memory leak. delete p_dsc; //// If you un-commented this, then you would have a double delete, //// which would crash your program. //delete p_dsc; //// If you did this, you would have a program error, which may or may //// not crash your program. Since dsc is not an array, it should not //// use the array delete (i.e. delete[]), but should use the non-array //// delete shown previously. //delete[] p_dsc; // You should always set a pointer to nullptr after deleting it to // prevent any accidental use of it (since what it points to is unknown // at this point). p_dsc = nullptr; // If you forgot this, you would have a memory leak. If you used // 'delete' instead of 'delete[]' unknown bad things might happen. Some // implementations will overlook it while others would crash or do who // knows what else. delete[] p_arrInt; p_arrInt = nullptr; wcout << L"Ending program." << endl; return 0;
Pentru care este incomod să executați proba, iată rezultatul pe care îl primesc când rulez acest lucru dintr-un prompt de comandă pe Windows 8 Release Preview, compilat cu Visual Studio 2012 Ultimate RC în configurația de depanare care vizează chipset-ul x86. Veți produce, probabil, valori diferite pentru adresele și codurile de thread dacă le executați pe propriul sistem.
Crearea instanței SomeClass. StringId: s_someClass. Adresa este: '0x009fade8'. Valoarea este '10'. ID-ul firului: '3660'. Crearea instanței SomeClass. StringId: g_staticSomeClass. Adresa este: '0x013f8554'. Valoarea este '20'. ID-ul firului: '3660'. Crearea instanței SomeClass. StringId: _pmain sc1. Adresa este: '0x007bfe98'. Valoarea este '1'. ID-ul firului: '3660'. Valoarea sc1: '1'. Crearea instanței SomeClass. StringId: _pmain sc2. Adresa este: '0x007bfe70'. Valoarea este '2'. ID-ul firului: '3660'. Valoarea sc2: '2'. Distrugerea instanței de tip SomeClass. StringId: _pmain sc2. Adresa este: '0x007bfe70'. ID-ul firului: '3660'. Crearea instanței SomeClass. StringId: LocalStatic sc. Adresa este: '0x013f8578'. Valoarea este '1000'. ID-ul firului: '3660'. Valoarea Statică locală sc: '1000'. Valoarea Statică locală sc: '1000'. Crearea instanței SomeClass. StringId: ChangeAndPrintValue fișier id: '3660'. Adresa este: '0x007bfbf4'. Valoarea este '20'. ID-ul firului: '3660'. Valoarea veche este 0. ID-ul firului: '3660'. Valoarea nouă este 20. Codul fișierului: '3660'. Distrugerea instanței de tip SomeClass. StringId: ChangeAndPrintValue fișier id: '3660'. Adresa este: '0x007bfbf4'. ID-ul firului: '3660'. Crearea instanței SomeClass. StringId: ChangeAndPrintValue fișier id: '5796'. Adresa este: '0x0045faa8'. Valoarea este '40'. ID-ul firului: '5796'. Valoarea veche este 0. Cod de thread: '5796'. Valoarea nouă este de 40. ID-ul firului: '5796'. Distrugerea instanței de tip SomeClass. StringId: ChangeAndPrintValue fișier id: '5796'. Adresa este: '0x0045faa8'. ID-ul firului: '5796'. Crearea instanței SomeClass. StringId: _pmain p_dsc. Adresa este: '0x009fbcc0'. Valoarea este '1000'. ID-ul firului: '3660'. i: "0". p_arrInt [i] = '-842150451'. i: "1". p_arrInt [i] = '-842150451'. i: "2". p_arrInt [i] = '-842150451'. i: "3". p_arrInt [i] = '-842150451'. i: "4". p_arrInt [i] = '-842150451'. i: "0". p_arrInt [i] = '0'. i: "1". p_arrInt [i] = '1'. i: "2". p_arrInt [i] = '2'. i: "3". p_arrInt [i] = '3'. i: "4". p_arrInt [i] = '4'. Distrugerea instanței de tip SomeClass. StringId: _pmain p_dsc. Adresa este: '0x009fbcc0'. ID-ul firului: '3660'. Finalizează programul. Distrugerea instanței de tip SomeClass. StringId: _pmain sc1. Adresa este: '0x007bfe98'. ID-ul firului: '3660'. Distrugerea instanței de tip SomeClass. StringId: LocalStatic sc. Adresa este: '0x013f8578'. ID-ul firului: '3660'. Distrugerea instanței de tip SomeClass. StringId: g_staticSomeClass. Adresa este: '0x013f8554'. ID-ul firului: '3660'. Distrugerea instanței de tip SomeClass. StringId: s_someClass. Adresa este: '0x009fade8'. ID-ul firului: '3660'.
Durata de stocare este un alt aspect important al C ++, astfel asigurați-vă că aveți o bună înțelegere a ceea ce am discutat în acest articol înainte de a vă deplasa. Următoarele sunt constructorii, distrugătorii și operatorii.
Această lecție reprezintă un capitol din C ++ Succinctly, o carte electronică gratuită de la echipa de la Syncfusion.