Om de C ++ -taalstandaard te citeren: "Opslagduur is de eigenschap van een object dat de minimale potentiële levensduur van de opslagruimte met het object definieert." Kort gezegd, dit is wat u vertelt hoe lang u zou verwachten dat een variabele bruikbaar is. De variabele kan een fundamenteel type zijn, zoals een int of een complex type, zoals een klasse. Ongeacht het type, een variabele is alleen gegarandeerd gegarandeerd zo lang als de programmeertaal zegt dat het zou moeten.
C ++ beheert het geheugen heel anders dan C #. Om te beginnen is er geen vereiste om een garbage collector te hebben en maar een paar implementaties. Voor zover C ++ -implementaties automatisch geheugenbeheer hebben, doen ze dit meestal via slimme aanwijzers en referentietellingen. C ++ -klassen worden niet automatisch op een heap (GC-beheerd of anderszins) uitgevoerd. In plaats daarvan werken ze veel meer als structuren in C #.
U kunt een instantie van een C ++ -klassen op een hoop duwen wanneer dat nodig is, maar als u het lokaal declareert en niets grappigs doet, heeft het een automatische duur, meestal geïmplementeerd met een stapel, en wordt het automatisch vernietigd wanneer de programma verlaat de scope waarin de klasse bestaat.
C ++ geeft u meer controle over geheugenbeheer dan C #. Een gevolg hiervan is dat de C ++ -taal en runtime-omgeving niet zoveel kunnen doen om foutieve code te voorkomen als de C # -taal en de CLR. Een van de sleutels tot een goede C ++ -programmeur is om te begrijpen hoe geheugenbeheer werkt en om de beste werkwijzen te gebruiken om efficiënte, juiste code te schrijven.
Globale variabelen, inclusief variabelen binnen naamruimten en variabelen die zijn gemarkeerd met het statische-sleutelwoordduur, hebben een statische opslagduur.
Globale variabelen worden geïnitialiseerd tijdens de programma-initialisatie (d.w.z. de periode voordat het programma daadwerkelijk de uitvoering van uw hoofd- of wmain-functie start). Ze worden geïnitialiseerd in de volgorde waarin ze in de broncode zijn gedefinieerd. Het is over het algemeen geen goed idee om te vertrouwen op een initialisatieorder, omdat refactoring en andere schijnbaar onschuldige wijzigingen gemakkelijk een potentiële bug in uw programma kunnen introduceren..
Lokale statische gegevens zijn geïnitialiseerd wanneer de eerste keer dat het programma wordt uitgevoerd, het blok met de lokale statische elektriciteit wordt bereikt. Doorgaans zullen ze worden geïnitialiseerd naar hun opgegeven waarden of worden geïnitialiseerd door op dat moment de opgegeven constructor aan te roepen. De waardetoekenning of bouwfase is niet vereist totdat het programma de verklaring bereikt en uitvoert, behalve in zeer zeldzame omstandigheden. Nadat een lokale statische eenheid is geïnitialiseerd, zal de initialisatie die is opgegeven met de declaratie nooit opnieuw worden uitgevoerd. Dit is natuurlijk precies wat we van een lokale statische elektriciteit zouden verwachten. Als het zichzelf telkens zou initialiseren als het programma zijn definitielijn zou bereiken, zou het hetzelfde zijn als een niet-statische lokaal.
Je kunt andere waarden aan globale en lokale statica toewijzen, tenzij je ze ook const maakt, natuurlijk.
Binnen een blok heeft een object een automatische duur als het zonder de nieuwe operator is gedefinieerd om het te instantiëren en zonder een sleutelwoord voor de opslagduur, hoewel het optioneel het registerzoekwoord kan hebben. Dit betekent dat het object wordt gemaakt op het moment dat het wordt gedefinieerd en wordt vernietigd wanneer het programma het blok verlaat waarvan de variabele is gedeclareerd, of wanneer een nieuwe waarde is toegewezen aan de variabele.
Notitie: Het automatische sleutelwoord was vroeger een manier om expliciet de automatische opslagduur te selecteren. In C ++ 11 is dat gebruik verwijderd. Het is nu het equivalent van het var-trefwoord in C #. Als u probeert iets te compileren met de oude betekenis van auto, ontvangt u een compileerfout omdat automatisch als een type-specificatie de enige typespecificatie moet zijn.
Dynamische duur is het resultaat van het gebruik van de nieuwe operator of de nieuwe [] -operator. De nieuwe operator wordt gebruikt om afzonderlijke objecten toe te wijzen, terwijl de nieuwe [] -operator wordt gebruikt om dynamische arrays toe te wijzen. U moet de grootte van een dynamisch toegewezen array bijhouden. Hoewel de C ++ -implementatie een dynamisch toegewezen array behoorlijk zal bevrijden, mits u de delete [] -operator gebruikt, is er geen gemakkelijke of draagbare manier om de grootte van die toewijzing te bepalen. Het zal waarschijnlijk onmogelijk zijn. Enkele objecten worden bevrijd met de delete-operator.
Wanneer u geheugen toewijst met nieuwe of nieuwe [], is de geretourneerde waarde een aanwijzer. Een aanwijzer is een variabele die een geheugenadres bevat. Als u in C # al uw verwijzingen naar een object instelt op nul of een andere waarde, dan is het geheugen niet meer bereikbaar in uw programma, zodat de GC dat geheugen kan vrijmaken voor ander gebruik.
Als u in C ++ alle aanwijzers naar een object op nullptr of een andere waarde instelt en u het oorspronkelijke adres niet kunt berekenen met behulp van pointer-rekenkundige bewerkingen, dan bent u uw vermogen kwijtgeraakt om dat geheugen vrij te geven met de operators delete of delete [] . U hebt daarmee een geheugenlek gecreëerd. Als een programma voldoende geheugen lekt, zal het uiteindelijk crashen omdat het systeem hiervoor onvoldoende geheugenadressen heeft. Maar zelfs daarvoor zal de computer vreselijk langzamer gaan werken, omdat het wordt gedwongen om de paging te verhogen om tegemoet te komen aan de steeds groter wordende geheugenvoetafdruk van je programma (ervan uitgaande dat het virtueel geheugen heeft, wat afwezig is op de meeste smartphones).
Notitie: Een const pointer, zoals someStr in de instructie const wchar_t * someStr = L "Hallo wereld!";
is geen dynamische duurwijzer. Die herinnering is slechts een deel van het programma zelf. Als u probeert te verwijderen of [] erop te verwijderen, zal het programma gewoon crashen. Een tekenreeks is echter een reeks tekens, dus als het OK zou zijn om het te verwijderen, dan zou de delete [] -operator de juiste zijn om te gebruiken.
Bij het omgaan met dynamisch geheugen, om mogelijke lekken te elimineren en de mogelijkheid van andere gerelateerde bugs te beperken, moet u altijd een slimme aanwijzer gebruiken, zoals std :: unique_ptr
of std :: shared_ptr
. We zullen deze bespreken in het hoofdstuk dat pointers behandelt.
De duur van de draad is de minst gebruikte opslagduur. Het is pas recent gestandaardiseerd. Op dit moment hebben enkele of geen C ++ -compilerververanciers ondersteuning geïmplementeerd voor het nieuwe thread_local-trefwoord uit de C ++ 11-standaard.
Dit is zeker om te veranderen, maar voor nu kun je leverancierspecifieke uitbreidingen gebruiken, zoals de Microsoft-specifieke extensie _declspec (thread) of de GCC-specifieke extensie __thread als je functionaliteit van dit soort nodig hebt.
De duur van de thread lijkt op de statische duur, behalve dat de duur van het programma niet wordt verlengd. Deze variabelen zijn lokaal voor elke thread; het exemplaar van de thread bestaat voor de duur van de thread. Elke thread-instantie van een thread-durationobject wordt geïnitialiseerd wanneer het voor het eerst wordt gebruikt in de thread en het wordt vernietigd wanneer de thread wordt afgesloten. Een thread durationobject ervaart de waarde niet van de thread waarmee de thread is gestart waarin deze bestaat.
Automatische opslagduur is meestal de juiste vorm van opslagduur voor objecten, tenzij u ze nodig hebt om het bereik te overleven waarin ze zijn gemaakt. Als dat het geval is, moet u een van de resterende opslagduur kiezen die het beste bij u past.
U kunt van deze aanbevelingen afwijken als dit zinvol is, maar in de meeste gevallen zal deze begeleiding u correct sturen.
Het volgende voorbeeld is opgenomen om deze begrippen te verduidelijken. Het monster is zwaar gedocumenteerd, dus geen extra commentaar inbegrepen. Ik moedig je sterk aan om dit specifieke monster te bouwen en uit te voeren. Als u de uitvoer ziet tijdens het doorlopen van de code, kunt u deze concepten gemakkelijker begrijpen dan alleen het lezen van de code.
Voorbeeld: StorageDurationSample \ SomeClass.h
#pragma één keer # opnemen#include class SomeClass public: explicit SomeClass (int value = 0); SomeClass (int value, const wchar_t * stringId); ~ SomeClass (void); int GetValue (void) return m_value; void SetValue (int waarde) m_value = waarde; static std :: unique_ptr s_someClass; privé: int m_value; std :: wstring m_stringId; ;
Voorbeeld: StorageDurationSample \ SomeClass.cpp
#include "SomeClass.h" #include#include #include #include #include #include #include namespace std; gebruiken; SomeClass :: SomeClass (int-waarde): m_value (waarde), m_stringId (L "(Geen tekenreeks-id opgegeven.)") 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 'voordat je doorgaat naar de //' SomeClass :: s_someClass = ...; ' waarde toewijzing. unique_ptr SomeClass :: s_someClass = unique_ptr (nieuw SomeClass (10, L "s_someClass"));
Voorbeeld: StorageDurationSample \ StorageDurationSample.cpp
#include#include #include #include #include #include # include "SomeClass.h" # include "... /pchar.h" met behulp van namespace std; struct SomeStruct int Value; ; naamruimte Waarde // Visual C ++ ondersteunt thread_local niet vanaf VS 2012 RC. We kunnen // thread_local gedeeltelijk nadoen met _declspec (thread), maar we kunnen geen dingen hebben als klassen met functies (inclusief constructors // en destructors) met _declspec (thread). _declspec (thread) SomeStruct ThreadLocalSomeStruct = ; // g_staticSomeClass heeft een statische duur. Het bestaat totdat het programma // eindigt of totdat er een andere waarde aan is toegewezen. Zelfs als u // hebt verlaten van het statische zoekwoord, zou het in dit geval nog steeds statisch zijn, omdat // het geen lokale variabele is, niet dynamisch is en geen thread- // lokale variabele is. static SomeClass g_staticSomeClass = SomeClass (20, L "g_staticSomeClass"); // Deze methode maakt een instantie SomeClass en wijzigt de // -waarde. void ChangeAndPrintValue (int value) // Creëer een identificatiereeks. 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;
Voor wie het lastig is om het voorbeeld uit te voeren, is hier de uitvoer die ik krijg als ik dit uitvoer vanaf een opdrachtprompt in Windows 8 Release Preview, gecompileerd met Visual Studio 2012 Ultimate RC in configuratie voor foutopsporing gericht op de x86-chipset. U zult waarschijnlijk andere waarden voor de adressen en thread-ID's produceren als u deze op uw eigen systeem uitvoert.
SomeClass-instantie maken. StringId: s_someClass. Adres is: '0x009fade8'. De waarde is '10'. Discussie id: '3660'. SomeClass-instantie maken. StringId: g_staticSomeClass. Adres is: '0x013f8554'. De waarde is '20'. Discussie id: '3660'. SomeClass-instantie maken. StringId: _pmain sc1. Adres is: '0x007bfe98'. De waarde is '1'. Discussie id: '3660'. sc1-waarde: '1'. SomeClass-instantie maken. StringId: _pmain sc2. Adres is: '0x007bfe70'. Waarde is '2'. Discussie id: '3660'. sc2-waarde: '2'. De SomeClass-instantie vernietigen. StringId: _pmain sc2. Adres is: '0x007bfe70'. Discussie id: '3660'. SomeClass-instantie maken. StringId: LocalStatic sc. Adres is: '0x013f8578'. De waarde is '1000'. Discussie id: '3660'. Lokale statische sc-waarde: '1000'. Lokale statische sc-waarde: '1000'. SomeClass-instantie maken. StringId: ChangeAndPrintWaarde-thread-ID: '3660'. Adres is: '0x007bfbf4'. De waarde is '20'. Discussie id: '3660'. Oude waarde is 0. Discussie id: '3660'. Nieuwe waarde is 20. Thread id: '3660'. De SomeClass-instantie vernietigen. StringId: ChangeAndPrintWaarde-thread-ID: '3660'. Adres is: '0x007bfbf4'. Discussie id: '3660'. SomeClass-instantie maken. StringId: ChangeAndPrintWaarde thread-id: '5796'. Adres is: '0x0045faa8'. De waarde is '40'. Discussie id: '5796'. Oude waarde is 0. Discussie id: '5796'. Nieuwe waarde is 40. Thread id: '5796'. De SomeClass-instantie vernietigen. StringId: ChangeAndPrintWaarde thread-id: '5796'. Adres is: '0x0045faa8'. Discussie id: '5796'. SomeClass-instantie maken. StringId: _pmain p_dsc. Adres is: '0x009fbcc0'. De waarde is '1000'. Discussie id: '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'. De SomeClass-instantie vernietigen. StringId: _pmain p_dsc. Adres is: '0x009fbcc0'. Discussie id: '3660'. Programma beëindigen. De SomeClass-instantie vernietigen. StringId: _pmain sc1. Adres is: '0x007bfe98'. Discussie id: '3660'. De SomeClass-instantie vernietigen. StringId: LocalStatic sc. Adres is: '0x013f8578'. Discussie id: '3660'. De SomeClass-instantie vernietigen. StringId: g_staticSomeClass. Adres is: '0x013f8554'. Discussie id: '3660'. De SomeClass-instantie vernietigen. StringId: s_someClass. Adres is: '0x009fade8'. Discussie id: '3660'.
Opslagduur is een ander belangrijk aspect van C ++, dus zorg ervoor dat u goed begrijpt wat we in dit artikel hebben besproken voordat u verder gaat. De volgende zijn constructeurs, destructors en operators.
Deze les staat voor een hoofdstuk uit C ++ Kort gezegd, een gratis eBoek van het team van Syncfusion.