C ++ Succinctly Lagringsvarighet

Introduksjon

For å sitere C ++ Language Standard, "Lagringsvarighet er egenskapen til et objekt som definerer minimumspotensialet for lagringen som inneholder objektet." I utgangspunktet er det hva som forteller deg hvor lenge du forventer at en variabel skal kunne brukes. Variabelen kan være en grunnleggende type, for eksempel en int, eller en kompleks type, for eksempel en klasse. Uansett hvilken type, en variabel bare er garantert å vare så lenge programmeringsspråket sier det burde.

C ++ styrer minnet veldig forskjellig fra C #. For en ting er det ikke krav om å ha søppelkollektor, og få implementeringer gir en. I den utstrekning at C + + implementeringer har automatisk minnehåndtering, gjør de det meste gjennom smarte pekere og referanse telling. C ++-klassene bor ikke automatisk på en bunke (GC-styrt eller på annen måte). I stedet jobber de mye mer som strukturer i C #.

Du kan skyve en C ++-klasse-forekomst på en haug når du trenger, men hvis du erklære det lokalt og ikke gjør noe morsomt, vil det ha en automatisk varighet, vanligvis implementert ved hjelp av en stabel, og vil automatisk bli ødelagt når programmet etterlater omfanget der klassen eksisterer.

C ++ gir deg mer kontroll over minnehåndtering enn C #. En konsekvens av dette er at C ++-språket og runtime-miljøet ikke kan gjøre så mye for å forhindre feil kode som C # -språket og CLR kan. En av nøklene til å være en god C ++-programmerer, er å forstå hvordan minnehåndtering fungerer og å bruke de beste metodene for å skrive effektiv, korrekt kode.


Statisk varighet

Globale variabler, inkludert de som befinner seg i navneområder, og variabler merket med varighetssøkeordet statisk, har statisk lagringsvarighet.

Globale variabler initialiseres under programinitialisering (dvs. perioden før programmet faktisk starter kjøringen av hoved- eller wmain-funksjonen). De initialiseres i den rekkefølge de er definert i kildekoden. Det er generelt ikke en god ide å stole på initialiseringsordre siden refactoring og andre tilsynelatende uskyldige endringer kan enkelt introdusere en potensiell feil i programmet ditt.

Lokal statikk er nullinitialisert første gang programutførelsen når blokken som inneholder lokal statisk. Vanligvis vil de initialiseres til sine angitte verdier eller initialiseres ved å ringe den angitte konstruktøren på det tidspunktet. Verdifallet eller byggfasen er ikke nødvendig før programmet når og utfører erklæringen, unntatt i svært sjeldne tilfeller. Når en lokal statisk er initialisert, vil initialiseringen spesifisert med sin deklarasjon aldri løpe igjen. Dette er selvsagt akkurat det vi ville forvente av en lokal statisk. Hvis den fortsatte å initialisere seg hver gang programmet nådde sin definisjonslinje, ville det være det samme som en ikke-statisk lokal.

Du kan tilordne andre verdier til global og lokal statistikk, med mindre du også gjør dem til en selvfølge.


Automatisk varighet

Innenfor en blokk har en gjenstand automatisk varighet hvis den er definert uten at den nye operatøren skal ordne det, og uten et varighet for søkeord, selv om det eventuelt kan ha registersøkeordet. Dette betyr at objektet er opprettet når det er definert og ødelagt når programmet går ut av blokken, variabelen ble oppgitt, eller når en ny verdi er tilordnet sin variabel.

Merk: Det automatiske søkeordet pleide å være en måte å eksplisitt velge automatisk lagringsvarighet på. I C + + 11 ble den bruken fjernet. Det er nå ekvivalent til var-nøkkelordet i C #. Hvis du prøver å kompilere noe ved hjelp av den gamle betydningen av auto, vil du motta en kompilatorfeil siden automatisk som en type spesifiserer må være den eneste typen spesifiserer.


Dynamisk varighet

Dynamisk varighet er resultatet av å bruke enten den nye operatøren eller den nye [] operatøren. Den nye operatøren brukes til å tilordne enkeltobjekter, mens den nye [] -operatoren brukes til å tildele dynamiske arrays. Du må holde styr på størrelsen på en dynamisk allokert rekkefølge. Mens C ++-implementeringen vil frigjøre en dynamisk tilordnet rekkefølge, forutsatt at du bruker slettingen [] -operatøren, er det ingen enkel eller bærbar måte å bestemme størrelsen på den tildelingen på. Det vil trolig være umulig. Enkeltobjekter frigjøres med slettoperatøren.

Når du tilordner minne ved hjelp av nytt eller nytt [], er returverdi en peker. En peker er en variabel som har en minneadresse. I C #, hvis du setter alle referanser til et objekt til null eller en annen verdi, er minnet ikke lenger tilgjengelig i programmet ditt, slik at GC kan frigjøre det minnet til andre bruksområder.

I C ++, hvis du setter alle poengene dine til en gjenstand til nullptr eller annen verdi, og du ikke kan finne ut den opprinnelige adressen ved hjelp av pekeralgetikk, har du mistet muligheten til å frigjøre det minnet ved å slette eller slette [] operatørene . Du har dermed skapt en minnelekkasje. Hvis et program lekker nok minne, vil det til slutt krasje fordi systemet vil gå tom for minneadresser for det. Selv før det, vil datamaskinen sakte forferdelig, da det blir tvunget til å øke personsøkingen for å imøtekomme det stadig økende minnekortet på programmet ditt (forutsatt at det har virtuelt minne, som er fraværende fra de fleste smarte telefoner).

Merk: En const peker, som noenStr i uttalelsen const wchar_t * someStr = L "Hello World!"; er ikke en dynamisk varighet pointer. Det minnet er bare en del av selve programmet. Hvis du prøver å ringe slette eller slette [] på det, vil programmet bare krasje. En streng er en rekke tegn, men hvis det var greit å slette det, ville delete [] -operatøren være den rette til å bruke.

Når du arbeider med dynamisk minne, for å eliminere potensielle lekkasjer og begrense muligheten for andre relaterte feil, bør du alltid bruke en smart peker som std :: unique_ptr eller std :: shared_ptr. Vi vil diskutere disse i kapittelet som dekker pekere.


Trådvarighet

Trådvarighet er den minst brukte lagringsvarigheten. Det har bare nylig blitt standardisert. I følge dette skrivet har få, hvis noen, C ++-kompilatorselgere implementert støtte for det nye thread_local søkeordet fra C ++ 11-standarden.

Dette er sikkert å endre, men for nå kan du bruke leverandørspesifikke utvidelser, for eksempel den Microsoft-spesifikke utvidelsen _declspec (tråd) eller den GCC-spesifikke utvidelsen __thread hvis du trenger funksjonalitet av denne typen.

Trådvarigheten er lik statisk varighet, bortsett fra i stedet for varigheten av programmet, er disse variablene lokale for hver tråd; Trådens kopi eksisterer for hele tråden. Hver tråds forekomst av en trådvarighetsobjekt initieres når den først brukes i tråden, og den blir ødelagt når tråden går ut. En trådvarighetsobjekt arver ikke verdien fra tråden som startet tråden den eksisterer i.


Velge riktig lagringsvarighet

Automatisk lagringsvarighet er vanligvis den riktige formen for lagringsvarighet for objekter, med mindre du trenger dem for å overleve omfanget de ble opprettet i. Hvis det er tilfelle, bør du velge en av de gjenværende lagringsvarighetene som passer best for dine behov.

  • Hvis objektet skal eksistere for hele lengden av programmets utførelse, bruk statisk lagringsvarighet.
  • Hvis objektet skal eksistere for hele lengden på en bestemt tråd, bruk trådenes varighet.
  • Hvis objektet bare eksisterer for en del av programmet eller trådenes varighet, bruk dynamisk lagringsvarighet.

Du kan avvike fra disse anbefalingene hvis det er fornuftig, men i de fleste tilfeller vil denne veiledningen styre deg riktig.


Lagringsvarighet Eksempel

Følgende eksempel er inkludert for å bidra til å klargjøre disse konseptene. Prøven er tungt dokumentert, så ingen tilleggs kommentar er inkludert. Jeg oppfordrer deg sterkt til å bygge og drive dette bestemte eksemplet. Å se produksjonen mens du går gjennom koden, hjelper deg å forstå disse konseptene lettere enn å bare lese koden.

Eksempel: StorageDurationSample \ SomeClass.h

#pragma en gang #include  #inkludere  klasse SomeClass offentlig: eksplisitt SomeClass (int verdi = 0); SomeClass (int verdi, const wchar_t * stringId); ~ SomeClass (void); int GetValue (tomrom) return m_value;  void SetValue (int verdi) m_value = value;  statisk std :: unique_ptr s_someClass; privat: int m_value; std :: wstring m_stringId; ;

Eksempel: StorageDurationSample \ SomeClass.cpp

#include "SomeClass.h" #include  #inkludere  #inkludere  #inkludere  #inkludere  #inkludere  #inkludere  bruker namespace std; SomeClass :: SomeClass (int verdi): m_value (verdi), m_stringId (L "(Ingen streng ID gitt.)") 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'før du fortsetter til //' SomeClass :: s_someClass = ...; ' verdioppgave. unique_ptr SomeClass :: s_someClass = unique_ptr(ny SomeClass (10, L "s_someClass"));

Eksempel: StorageDurationSample \ StorageDurationSample.cpp

#inkludere  #inkludere  #inkludere  #inkludere  #inkludere  #inkludere  #include "SomeClass.h" # include "... /pchar.h" ved hjelp av namespace std; struct SomeStruct int Value; ; namespace Verdi // Visual C ++ støtter ikke thread_local fra VS 2012 RC. Vi kan / / delvis etterligne thread_local med _declspec (tråd), men vi kan ikke // ha ting som klasser med funksjoner (inkludert constructors // og destructors) med _declspec (thread). _declspec (tråd) SomeStruct ThreadLocalSomeStruct = ; // g_staticSomeClass har statisk varighet. Den eksisterer til programmet // slutter eller til en annen verdi er tildelt den. Selv om du forlot // av det statiske søkeordet, vil det i så fall fortsatt være statisk siden // det er ikke en lokal variabel, ikke dynamisk, og er ikke en tråd- // lokal variabel. statisk SomeClass g_staticSomeClass = SomeClass (20, L "g_staticSomeClass");  // Denne metoden oppretter en SomeClass-forekomst, og endrer deretter //-verdien. void ChangeAndPrintValue (int verdi) // Opprett en identifiseringsstreng. 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; 

For hvem det er ubeleilig å kjøre prøven, her er utdataene jeg får når jeg kjører dette fra en kommandoprompt på Windows 8 Release Preview, kompilert med Visual Studio 2012 Ultimate RC i Debug-konfigurasjon rettet mot x86-brikkesettet. Du vil sannsynligvis produsere forskjellige verdier for adressene og tråd-IDene hvis du kjører den på ditt eget system.

Oppretter SomeClass-forekomst. StringId: s_someClass. Adresse er: '0x009fade8'. Verdien er '10'. Tråd id: '3660'. Oppretter SomeClass-forekomst. StringId: g_staticSomeClass. Adresse er: '0x013f8554'. Verdien er '20'. Tråd id: '3660'. Oppretter SomeClass-forekomst. StringId: _pmain sc1. Adresse er: '0x007bfe98'. Verdien er '1'. Tråd id: '3660'. sc1 verdi: '1'. Oppretter SomeClass-forekomst. StringId: _pmain sc2. Adresse er: '0x007bfe70'. Verdien er '2'. Tråd id: '3660'. sc2 verdi: '2'. Ødelegg SomeClass-forekomst. StringId: _pmain sc2. Adresse er: '0x007bfe70'. Tråd id: '3660'. Oppretter SomeClass-forekomst. StringId: LocalStatic sc. Adresse er: '0x013f8578'. Verdien er '1000'. Tråd id: '3660'. Lokal statisk sc-verdi: '1000'. Lokal statisk sc-verdi: '1000'. Oppretter SomeClass-forekomst. StringId: ChangeAndPrintValue tråd id: '3660'. Adresse er: '0x007bfbf4'. Verdien er '20'. Tråd id: '3660'. Gamle verdi er 0. Tråd id: '3660'. Ny verdi er 20. Tråd id: '3660'. Ødelegg SomeClass-forekomst. StringId: ChangeAndPrintValue tråd id: '3660'. Adresse er: '0x007bfbf4'. Tråd id: '3660'. Oppretter SomeClass-forekomst. StringId: ChangeAndPrintValue tråd id: '5796'. Adresse er: '0x0045faa8'. Verdien er '40'. Tråd id: '5796'. Gamle verdi er 0. Tråd id: '5796'. Ny verdi er 40. Tråd id: '5796'. Ødelegg SomeClass-forekomst. StringId: ChangeAndPrintValue tråd id: '5796'. Adresse er: '0x0045faa8'. Tråd id: '5796'. Oppretter SomeClass-forekomst. StringId: _pmain p_dsc. Adresse er: '0x009fbcc0'. Verdien er '1000'. Tråd id: '3660'. jeg: '0'. p_arrInt [i] = '-842150451'. jeg: '1'. p_arrInt [i] = '-842150451'. jeg: '2'. p_arrInt [i] = '-842150451'. jeg: '3'. p_arrInt [i] = '-842150451'. jeg: '4'. p_arrInt [i] = '-842150451'. jeg: '0'. p_arrInt [i] = '0'. jeg: '1'. p_arrInt [i] = '1'. jeg: '2'. p_arrInt [i] = '2'. jeg: '3'. p_arrInt [i] = '3'. jeg: '4'. p_arrInt [i] = '4'. Ødelegg SomeClass-forekomst. StringId: _pmain p_dsc. Adresse er: '0x009fbcc0'. Tråd id: '3660'. Sluttprogram. Ødelegg SomeClass-forekomst. StringId: _pmain sc1. Adresse er: '0x007bfe98'. Tråd id: '3660'. Ødelegg SomeClass-forekomst. StringId: LocalStatic sc. Adresse er: '0x013f8578'. Tråd id: '3660'. Ødelegg SomeClass-forekomst. StringId: g_staticSomeClass. Adresse er: '0x013f8554'. Tråd id: '3660'. Ødelegg SomeClass-forekomst. StringId: s_someClass. Adresse er: '0x009fade8'. Tråd id: '3660'.

Conclusiong

Lagringsvarighet er et annet viktig aspekt ved C ++, så sørg for at du har en god forståelse av det vi diskuterte i denne artikkelen før du fortsetter. Neste opp er konstruktører, destruktorer og operatører.

Denne leksjonen representerer et kapittel fra C ++ Succinctly, en gratis eBok fra teamet på Syncfusion.