C ++ Succinctly Ressursoppkjøp er initialisering

Hva er RAII?

RAII står for "ressursoppkjøp er initialisering." RAII er et mønster som bruker C ++-kode for å eliminere ressurslekkasjer. Ressurslekkasjer skje når en ressurs som programmet ditt kjøpte ikke senere blir gitt ut. Det mest kjente eksemplet er en minnelekkasje. Siden C ++ ikke har en GC måten C # gjør, må du være forsiktig med å sikre at dynamisk tildelt minne blir frigjort. Ellers vil du lekke det minnet. Ressurslekkasjer kan også resultere i manglende evne til å åpne en fil fordi filsystemet mener det allerede er åpent, manglende evne til å oppnå en lås i et multi-threaded program, eller manglende evne til å frigjøre en COM-gjenstand.


Hvordan virker RAII??

RAII fungerer på grunn av tre grunnleggende fakta.

  1. Når en automatisk lagringsvarighetsobjekt går utenfor omfanget, kjører destructoren.
  2. Når et unntak oppstår, blir alle automatiske varighetsobjekter som er bygget helt siden den siste prøveblokken startet, ødelagt i omvendt rekkefølge de ble opprettet før noen fangstbehandler er påkalt.
  3. Hvis du nester prøveblokker, og ingen av fangsthåndtererne av en indre prøveblokk håndterer den typen unntak, forplanter seg unntaket til den ytre prøveblokken. Alle automatiske varighetsobjekter som er fullt konstruert i den ytre prøveblokken, blir så ødelagt i omvendt opprettelsesordre før noen fangstbehandler påberopes, og så videre, til noe fanger unntaket eller programmet krasjer.

RAII bidrar til at du frigjør ressurser uten unntak, ved å bruke automatiske lagringsvarighetsobjekter som inneholder ressursene. Det ligner på kombinasjonen av System.IDisposable grensesnitt sammen med brukserklæringen i C #. Når utførelsen forlater nåværende blokk, enten gjennom vellykket utførelse eller unntak, blir ressursene frigjort.

Når det kommer til unntak, er en viktig del å huske at bare fullstendig konstruerte objekter blir ødelagt. Hvis du mottar et unntak midt i en konstruktør, og den siste prøveblokken begynte utenfor den konstruktøren, siden objektet ikke er fullt konstruert, vil dets destruktor ikke løpe.

Dette betyr ikke at medlemsvariablene, som er objekter, ikke vil bli ødelagt. Eventuelle elementvariabel objekter som var fullt konstruert i konstruktøren før unntaket oppstod, er fullt konstruerte automatiske varighetsobjekter. Dermed vil disse objektobjektene bli ødelagt det samme som alle andre fullt konstruerte objekter.

Dette er grunnen til at du alltid bør legge inn dynamiske tildelinger inni std :: unique_ptr eller std :: shared_ptr. Forekomster av disse typene blir fullt konstruerte objekter når tildelingen lykkes. Selv om konstruktøren for objektet du oppretter, mislykkes videre i std :: unique_ptr ressurser vil bli frigjort av destructor og std :: shared_ptr ressursene vil få deres referansetall redusert og vil bli frigjort dersom tallet blir null.

RAII handler ikke bare om shared_ptr og unique_ptr, selvfølgelig. Den gjelder også for andre ressurstyper, for eksempel et filobjekt, hvor oppkjøpet er åpningen av filen og destructoren sikrer at filen er ordentlig lukket. Dette er et spesielt godt eksempel, siden du bare trenger å lage denne koden riktig bare en gang - når du skriver klassen - i stedet for igjen og igjen, det er det du må gjøre hvis du skriver nøyaktig logikken hvert sted du må åpne en fil.


Hvordan bruker jeg RAII?

RAII-bruk er beskrevet ved navn: Å oppnå en dynamisk ressurs bør fullføre initialiseringen av et objekt. Hvis du følger dette ett ressurs-per-objekt-mønsteret, er det umulig å komme opp med en ressurslekkasje. Enten vil du med hell skaffe ressursen, i så fall vil objektet som innkapsler det, fullføre byggingen og bli gjenstand for ødeleggelse, eller oppkjøpsforsøket vil mislykkes, i hvilket tilfelle du ikke ervervet ressursen; Derfor er det ingen ressurs å frigjøre.

Destruktoren til et objekt som encapsulates en ressurs må frigjøre den ressursen. Dette er blant annet en av de viktigste årsakene til at destruktorer aldri skal kaste unntak, unntatt de de fanger og håndterer i seg selv.

Hvis destruktoren kastet et uncaught unntak, så, for å sitere Bjarne Stroustrup, "Alle slags dårlige ting vil trolig skje fordi de grunnleggende reglene for standardbiblioteket og språket i seg selv vil bli brutt. Ikke gjør det. "

Som han sa, ikke gjør det. Sørg for at du vet hvilke unntak, hvis noe, alt du ringer i dine destruktorer kan kaste slik at du kan sikre at du håndterer dem riktig.

Nå kan du tenke at hvis du følger dette mønsteret, vil du ende opp med å skrive massevis av klasser. Du vil av og til skrive en ekstra klasse her og der, men du er ikke sannsynlig å skrive for mange på grunn av smarte pekere. Smarte pekere er også objekter. De fleste typer dynamiske ressurser kan settes inn i minst en av de eksisterende smart pointer klassene. Når du legger inn et ressursoppkjøp inne i en egnet smart pointer, dersom oppkjøpet lykkes, blir det smarte pointerobjektet fullt konstruert. Hvis et unntak oppstår, blir det smarte pekerobjektets destructor kalt, og ressursen blir frigjort.

Det finnes flere viktige smarte pekertyper. La oss ta en titt på dem.

De std :: unique_ptr Funksjon

Den unike pekeren, std :: unique_ptr, er designet for å holde en peker til en dynamisk tildelt objekt. Du bør bare bruke denne typen når du vil ha en peker til objektet skal eksistere. Det er en mal klasse som tar et obligatorisk og et valgfritt mal argument. Det obligatoriske argumentet er typen pekeren den vil holde. For eksempel auto resultat = std :: unique_ptr(ny int ()); vil opprette en unik peker som inneholder en int *. Det valgfrie argumentet er typen av sletter. Vi ser hvordan du skriver en deleter i en kommende prøve. Vanligvis kan du unngå å spesifisere en deleter siden standard_deleter, som er gitt for deg hvis ingen deleter er angitt, dekker nesten alle tilfeller du kan forestille deg.

En klasse som har std :: unique_ptr som en variabel kan ikke ha en standard kopi konstruktør. Kopier semantikk er deaktivert for std :: unique_ptr. Hvis du vil ha en kopi-konstruktør i en klasse som har en unik peker, må du skrive den. Du bør også skrive en overbelastning for kopieringsoperatøren. Vanligvis vil du ha det std :: shared_ptr i så fall.

Du kan imidlertid ha noe som en rekke data. Du vil kanskje også ha noen kopi av klassen for å lage en kopi av dataene som den eksisterer på den tiden. I så fall kan en unik peker med en tilpasset kopi-konstruktør være det riktige valget.

std :: unique_ptr er definert i header fil.

std :: unique_ptr har fire medlemsfunksjoner av interesse.

Funksjonen get member returnerer den lagrede pekeren. Hvis du trenger å ringe en funksjon som du må passere den inneholdte pekeren til, kan du bruke å hente en kopi av pekeren.

Utløsningselementfunksjonen returnerer også den lagrede pekeren, men frigjør invalidiserer unique_ptr i prosessen ved å erstatte den lagrede pekeren med en nullpeker. Hvis du har en funksjon der du vil opprette et dynamisk objekt og deretter returnere det, samtidig som du opprettholder unntakssikkerhet, bruk std: unique_ptr å lagre det dynamisk opprettede objektet, og deretter returnere resultatet av å ringe utgivelsen. Dette gir deg unntakssikkerhet, samtidig som du lar deg returnere det dynamiske objektet uten å ødelegge det med std :: unique_ptrdestructor når kontrollen utgår fra funksjonen ved retur av den utgitte pointerverdien på slutten.

Byttefunksjonen tillater to unike pekere å utveksle sine lagrede pekere, så hvis A holder en peker til X, og B holder en peker til Y, blir resultatet av å ringe En :: swap (B); er det at A vil nå holde en peker til Y, og B vil holde en peker til X. Deletallene for hver blir også byttet, så hvis du har en tilpasset sletter for begge eller begge unike pekere, vær sikker på at hver vilje behold den tilhørende deleteringen.

Tilbakestillingsfunksjonen forårsaker at objektet peker på at den lagrede pekeren, om noen, vil bli ødelagt i de fleste tilfeller. Hvis den nåværende lagrede pekeren er null, blir ingenting ødelagt. Hvis du sender inn en peker til objektet som den nåværende lagrede pekeren peker på, blir ingenting ødelagt. Du kan velge å passere i en ny peker, nullptr, eller å ringe funksjonen uten parametere. Hvis du sender inn en ny peker, lagres det nye objektet. Hvis du passerer i nullptr, vil den unike pekeren lagre null. Å ringe funksjonen uten parametre er den samme som å kalle den med nullptr.

De std :: shared_ptr Funksjon

Den delte pekeren, std :: shared_ptr, er designet for å holde en peker til en dynamisk tildelt objekt og å holde en referanse telling for det. Det er ikke magisk; hvis du oppretter to delte poeng og sender dem hver en peker til det samme objektet, vil du ende opp med to delte poengere, hver med en referanseantall på 1, ikke 2. Den første som blir ødelagt, vil frigjøre den underliggende ressursen, gi katastrofale resultater når du prøver å bruke den andre eller når den andre er ødelagt, og prøver å frigjøre den allerede utgitte underliggende ressursen.

For å bruke den felles poengeren på riktig måte, opprett en forekomst med en objektpeker og opprett alle andre delte poengene for objektet fra en eksisterende, gyldig delt pointer for den objekten. Dette sikrer en felles referanse telling, slik at ressursen vil ha en riktig levetid. La oss se på en rask prøve for å se de riktige og gale måtene for å opprette shared_ptr-objekter.

Eksempel: SharedPtrSample \ SharedPtrSample.cpp

#inkludere  #inkludere  #inkludere  #include "... /pchar.h" ved hjelp av navneområde std; struct TwoInts TwoInts (void): A (), B ()  TwoInts (int a, int b): A (a), B (b)  int A; int B; ; wostream og operatør<<(wostream& stream, TwoInts* v)  stream << v->EN << L" " << v->B; retur strøm;  int _pmain (int / * argc * /, _pchar * / * argv * / []) //// Dårlig: resulterer i dobbelt fri. // Prøv // // TwoInts * p_i = new TwoInts (10, 20); // auto sp1 = shared_ptr(P_i); // auto sp2 = shared_ptr(P_i); // p_i = nullptr; // wcout << L"sp1 count is " << sp1.use_count() << L"." << endl << // L"sp2 count is " << sp2.use_count() << L"." << endl; // //catch(exception& e) // // wcout << L"There was an exception." << endl; // wcout << e.what() << endl << endl; // //catch(… ) // // wcout << L"There was an exception due to a double free " << // L"because we tried freeing p_i twice!" << endl; // // This is one right way to create shared_ptrs.  auto sp1 = shared_ptr(nye TwoInts (10, 20)); auto sp2 = shared_ptr(SP1); wcout << L"sp1 count is " << sp1.use_count() << L"." << endl << L"sp2 count is " << sp2.use_count() << L"." << endl; wcout << L"sp1 value is " << sp1 << L"." << endl << L"sp2 value is " << sp2 << L"." << endl;  // This is another right way. The std::make_shared function takes the // type as its template argument, and then the argument value(s) to the // constructor you want as its parameters, and it automatically // constructs the object for you. This is usually more memory- // efficient, as the reference count can be stored with the // shared_ptr's pointed-to object at the time of the object's creation.  auto sp1 = make_shared(10, 20); auto sp2 = shared_ptr(SP1); wcout << L"sp1 count is " << sp1.use_count() << L"." << endl << L"sp2 count is " << sp2.use_count() << L"." << endl; wcout << L"sp1 value is " << sp1 << L"." << endl << L"sp2 value is " << sp2 << L"." << endl;  return 0; 

std :: shared_ptr er definert i header fil.

std :: shared_ptr har fem medlemsfunksjoner av interesse.

Få-medlem-funksjonen fungerer på samme måte som std :: unique_ptr :: get member-funksjonen.

Brukerfunksjonen use_count-medlemmet returnerer en lang, som forteller deg hva gjeldende referanseantall for målobjektet er. Dette inkluderer ikke svake referanser.

Den unike medlemfunksjonen returnerer en bool, og informerer deg om denne bestemte delte pekeren er den eneste eieren av målobjektet.

Byttefunksjonen fungerer på samme måte som std :: unique_ptr :: swap medlemsfunksjon, med tillegg av at referansenummeret for ressursene forblir det samme.

Tilbakestillingsfunksjonen reduserer referansetellingen for den underliggende ressursen og ødelegger den hvis ressursantallet blir null. Hvis en peker til en gjenstand blir sendt inn, vil den delte pekeren lagre den og starte en ny referansetelling for pekeren. Hvis nullptr er sendt inn, eller hvis ingen parameter er bestått, vil den delte pekeren lagre null.

De std :: make_shared Funksjon

De std :: make_shared malfunksjon er en praktisk måte å konstruere en initial på std :: shared_ptr. Som vi så tidligere i SharedPtrSample, du overfører typen som malargumentet, og deretter bare passerer i argumenter, hvis noen, for ønsket konstruktør. std :: make_shared vil konstruere en haug forekomst av mal argumentet objekttype og gjøre det til en std :: shared_ptr. Du kan da passere det std :: shared_ptr som et argument til std :: shared_ptr konstruktør for å opprette flere referanser til det delte objektet.


ComPtr i WRL for Metro-Style Apps

Windows Runtime Template Library (WRL) gir en smart peker som heter ComPtr i Microsoft :: WRL navneområde for bruk med COM objekter i Windows 8 T-bane applikasjoner. Pekeren er funnet i header, som en del av Windows SDK (minimum versjon 8.0).

Det meste av operativsystemets funksjonalitet som du kan bruke i Metro-stil applikasjoner, er eksponert av Windows Runtime ("WinRT"). WinRT-objekter gir sin egen automatiske referanse telling funksjonalitet for objekt opprettelse og ødeleggelse. Enkelte systemfunksjoner, for eksempel Direct3D, krever at du direkte bruker og manipulerer den gjennom klassisk COM. ComPtr håndterer COMs ukjente referanse telling for deg. Det gir også praktiske wrappers for QueryInterface og inkluderer annen funksjonalitet som er nyttig for smarte pekere.

De to medlemsfunksjonene du vanligvis bruker, er som å få et annet grensesnitt for det underliggende COM-objektet, og Få til å ta en grensesnittpeker til det underliggende COM-objektet som ComPtr holder (dette er ekvivalent av std :: unique_ptr :: få).

Noen ganger vil du bruke Avslutt, som fungerer på samme måte som std :: unique_ptr :: release, men har et annet navn fordi utgivelsen i COM innebærer å senke referansetellingen og Avslutt ikke det.

Du kan bruke ReleaseAndGetAddressOf for situasjoner der du har en eksisterende ComPtr som allerede kunne ha et COM-objekt, og du vil erstatte det med et nytt COM-objekt av samme type. ReleaseAndGetAddressOf gjør det samme som GetAddressOf-medlemmet, men det frigir først det underliggende grensesnittet, hvis det er noe.


Unntak i C++

I motsetning til .NET, hvor alle unntakene kommer fra System.Exception og har garantert metoder og egenskaper, C ++ unntak er ikke nødvendig å utlede fra noe; De trenger heller ikke å være klassetyper. I C ++ kaster du L "Hello World!"; er helt akseptabelt for kompilatoren som er kaste 5 ;. I utgangspunktet kan unntak være noe.

Når det er sagt, vil mange C ++-programmerere være ulykkelige for å se et unntak som ikke kommer fra std :: unntak (funnet i Overskrift). Avlede alle unntak fra std :: unntak gir en måte å fange unntak av ukjent type og hente informasjon fra dem via hvilken medlemsfunksjon før de kastes igjen. std :: unntak :: hva tar ingen parametere og returnerer a const char * streng, som du kan se eller logge på så du vet hva som forårsaket unntaket.

Det er ingen stakkspor-ikke teller stablingssporegenskapene som debuggeren gir, med C ++-unntak. Fordi automatisk varighet gjenstander innenfor rekkevidden av prøveblokken som fanger unntaket, blir automatisk ødelagt før den aktuelle fangstshåndtereren, hvis noen, er aktivert, har du ikke lyst til å undersøke dataene som kan ha forårsaket unntaket. Alt du trenger å jobbe med er i utgangspunktet meldingen fra hvilken medlemsfunksjon.

Hvis det er enkelt å gjenskape forholdene som førte til unntaket, kan du angi et brytepunkt og gjenopprette programmet, slik at du kan gå gjennom utførelsen av problemområdet og muligens oppdage problemet. Fordi det ikke alltid er mulig, er det viktig å være så nøyaktig som mulig med feilmeldingen.

Når det kommer fra std :: unntak, bør du sørge for å overstyre hvilken medlemsfunksjon som gir en nyttig feilmelding som vil hjelpe deg og andre utviklere å finne ut hva som gikk galt.

Noen programmerere bruker en variant av en regel som sier at du alltid skal kaste std :: unntak-avledede unntak. Husker at inngangspunktet (main eller wmain) returnerer et heltall, vil disse programmererne kaste std :: unntak-avledede unntak når deres kode kan gjenopprette, men vil bare kaste en veldefinert heltall verdi dersom feilen ikke kan gjenopprettes. Inngangspunktkoden vil bli pakket inn i en prøveblokk som har en fangst for en int. Fangstbehandleren vil returnere fanget int verdi. På de fleste systemer betyr en returverdi på 0 fra et program suksess. Enhver annen verdi betyr feil.

Hvis det er en katastrofal feil, kan det være å gi noe mening ved å kaste en veldefinert heltallverdi enn 0. Med mindre du jobber med et prosjekt der dette er den foretrukne stilen, bør du holde fast ved det std :: unntak-avledede unntak, siden de la programmer håndtere unntak ved hjelp av et enkelt loggingssystem for å registrere meldinger fra unntak som ikke håndteres, og de utfører opprydding som er trygt. Kast noe som ikke kommer fra std :: unntak ville forstyrre disse feilloggingsmekanismer.

En siste ting å merke seg er at C # s endelig konstruksjon ikke har noe tilsvarende i C ++. RAII-idiomet, når det er riktig implementert, gjør det unødvendig siden alt har blitt ryddet opp.


C ++ Standard Library Exceptions

Vi har allerede diskutert std :: unntak, men det finnes flere typer enn det som er tilgjengelig i standardbiblioteket, og det er flere funksjoner å utforske. La oss se på funksjonaliteten fra header fil først.

De std :: avslutte funksjon, som standard, lar deg krasje av hvilket som helst program. Det bør brukes sparsomt, siden det kaller det i stedet for å kaste et unntak, vil omgå alle normale unntakshåndteringsmekanismer. Hvis du ønsker det, kan du skrive en egendefinert avslutningsfunksjon uten parametere og returverdier. Et eksempel på dette vil bli sett i ExceptionsSample, som kommer.

Hvis du vil angi egendefinert avslutning, ringer du std :: set_terminate og send det til adressen til funksjonen. Du kan når som helst endre den tilpassede avslutte handleren. Det siste funksjonssettet er det som skal ringes ved en samtale til std :: avslutte eller et uhåndtert unntak. Standardbehandleren kaller abort-funksjonen fra header fil.

De header gir et rudimentært rammeverk for unntak. Det definerer to klasser som arver fra std :: unntak. Disse to klassene tjener som overordnet klasse for flere andre klasser.

De std :: runtime_error klassen er foreldreklassen for unntak kastet av kjøretiden eller på grunn av en feil i en C ++ Standard Library-funksjon. Dens barn er std :: overflow_error klasse, den std :: range_error klassen og std :: underflow_error klasse.

De std :: logic_error Klassen er foreldreklassen for unntak kastet på grunn av programmeringsfeil. Dens barn er std :: domain_error klasse, den std :: invalid_argument klasse, den std :: length_error klassen og std :: out_of_range klasse.

Du kan utlede fra disse klassene eller lage dine egne unntaksklasser. Å komme opp med et godt unntakshierarki er en vanskelig oppgave. På den ene siden vil du ha unntak som vil være spesifikke nok til at du kan håndtere alle unntak basert på din kunnskap på byggetid. På den annen side vil du ikke ha en unntaksklasse for hver feil som kan oppstå. Koden din vil ende opp oppblåst og ubøyelig, for ikke å nevne sløsing med tid som skriver fangsthåndtere for hvert unntaksklasse.

Tilbring tid på en tavle, eller med en penn og papir, eller om du vil tenke på unntakstreet, bør søknaden din ha.

Følgende prøve inneholder en klasse som heter InvalidArgumentExceptionBase, som brukes som foreldre i en malklasse kalt InvalidArgumentException. Kombinasjonen av en baseklasse, som kan bli fanget med en unntakshåndterer og en maleklasse, som tillater oss å tilpasse utdatafysikene basert på typen av parameteren, er et alternativ for å balansere mellom spesialisering og kodeoppblåsing.

Malklassen kan virke forvirrende akkurat nå; Vi skal diskutere maler i et kommende kapittel, på hvilket tidspunkt alt som er uklart, bør rydde opp.

Eksempel: UnntakSample \ InvalidArgumentException.h

#pragma en gang #include  #inkludere  #inkludere  #inkludere  namespace CppForCsExceptions class InvalidArgumentExceptionBase: offentlig std :: invalid_argument public: InvalidArgumentExceptionBase (void): std :: invalid_argument ("")  virtuell ~ InvalidArgumentExceptionBase (void) kaste ()  virtuell const char * hva () overstyring = 0; ; mal  klasse InvalidArgumentException: offentlig InvalidArgumentExceptionBase public: inline InvalidArgumentException (const char * className, const char * funksjonSignatur, const char * parameternavn, T parameterValue); inline virtuell ~ InvalidArgumentException (void) kaste (); inline virtuelle const char * hva (void) const kaste () overstyre; privat: std :: streng m_whatMessage; ; mal InvalidArgumentException:: InvalidArgumentException (const char * className, const char * funksjonSignatur, const char * parameternavn, T parameterValue): InvalidArgumentExceptionBase (), m_whatMessage () std :: stringstream msg; msg << className << "::" << functionSignature << " - parameter '" << parameterName << "' had invalid value '" << parameterValue << "'."; m_whatMessage = std::string(msg.str());  template InvalidArgumentException:: ~ InvalidArgumentException (void) kaste ()  mal const char * InvalidArgumentException:: hva (void) const kaste () return m_whatMessage.c_str (); 

Eksempel: Unntak Sample \ UnntakSample.cpp

#inkludere  #inkludere  #inkludere  #inkludere  #inkludere  #inkludere  #inkludere  #inkludere  #include "InvalidArgumentException.h" #include "... /pchar.h" ved hjelp av navneområde CppForCsExceptions; bruker namespace std; klasse ThrowClass public: ThrowClass (void): m_shouldThrow (false) wcout << L"Constructing ThrowClass." << endl;  explicit ThrowClass(bool shouldThrow) : m_shouldThrow(shouldThrow)  wcout << L"Constructing ThrowClass. shouldThrow = " << (shouldThrow ? L"true." : L"false.") << endl; if (shouldThrow)  throw InvalidArgumentException("ThrowClass", "ThrowClass (bool shouldThrow)", "shouldThrow", "true");  ~ ThrowClass (void) wcout << L"Destroying ThrowClass." << endl;  const wchar_t* GetShouldThrow(void) const  return (m_shouldThrow ? L"True" : L"False");  private: bool m_shouldThrow; ; class RegularClass  public: RegularClass(void)  wcout << L"Constructing RegularClass." << endl;  ~RegularClass(void)  wcout << L"Destroying RegularClass." << endl;  ; class ContainStuffClass  public: ContainStuffClass(void) : m_regularClass(new RegularClass()), m_throwClass(new ThrowClass())  wcout << L"Constructing ContainStuffClass." << endl;  ContainStuffClass(const ContainStuffClass& other) : m_regularClass(new RegularClass(*other.m_regularClass)), m_throwClass(other.m_throwClass)  wcout << L"Copy constructing ContainStuffClass." << endl;  ~ContainStuffClass(void)  wcout << L"Destroying ContainStuffClass." << endl;  const wchar_t* GetString(void) const  return L"I'm a ContainStuffClass.";  private: unique_ptr m_regularClass; shared_ptr m_throwClass; ; void TerminateHandler (void) wcout << L"Terminating due to unhandled exception." << endl; // If you call abort (from ), vil programmet avslutte // unormalt. Det vil også gå ut unormalt hvis du ikke ringer / noe for å få den til å gå ut av denne metoden. avbryte(); //// Hvis du i stedet var å ringe exit (0) (også fra ), //// da programmet ditt skulle gå ut som om ingenting hadde //// gått galt. Dette er dårlig fordi noe gikk galt. //// Jeg presenterer dette slik at du vet at det er mulig for //// et program å kaste uncaught unntak og fortsatt //// exit på en måte som ikke tolkes som et krasj siden //// du må kanskje finne ut hvorfor et program holder brat //// spennende ennå ikke krasjer. Dette ville være en slik årsak //// for det. // utgang (0);  int _pmain (int / * argc * /, _pchar * / * argv * / []) // Angi en tilpasset handler for std :: terminate. Vær oppmerksom på at denne handleren // ikke vil kjøre med mindre du kjører den fra en ledetekst. Feilsøkingsprogrammet // vil avskjære det uhåndterte unntaket og vil presentere deg med // feilsøkingsalternativer når du kjører det fra Visual Studio. set_terminate (& TerminateHandler); prøv ContainStuffClass cSC; wcout << cSC.GetString() << endl; ThrowClass tC(false); wcout << L"tC should throw? " << tC.GetShouldThrow() << endl; tC = ThrowClass(true); wcout << L"tC should throw? " << tC.GetShouldThrow() << endl;  // One downside to using templates for exceptions is that you need a // catch handler for each specialization, unless you have a base // class they all inherit from, that is. To avoid catching // other std::invalid_argument exceptions, we created an abstract // class called InvalidArgumentExceptionBase, which serves solely to // act as the base class for all specializations of // InvalidArgumentException. Nå kan vi fange dem alle, hvis ønskelig, // uten å ha en fangsthåndterer for hver. Hvis du vil, kan du / / du fortsatt ha en handler for en bestemt spesialisering. fange (InvalidArgumentExceptionBase & e) wcout << L"Caught '" << typeid(e).name() << L"'." << endl << L"Message: " << e.what() << endl;  // Catch anything derived from std::exception that doesn't already // have a specialized handler. Since you don't know what this is, you // should catch it, log it, and re-throw it. catch (std::exception& e)  wcout << L"Caught '" << typeid(e).name() << L"'." << endl << L"Message: " << e.what() << endl; // Just a plain throw statement like this is a re-throw. throw;  // This next catch catches everything, regardless of type. Like // catching System.Exception, you should only catch this to // re-throw it. catch (… )  wcout << L"Caught unknown exception type." << endl; throw;  // This will cause our custom terminate handler to run. wcout << L"tC should throw? " << ThrowClass(true).GetShouldThrow() << endl; return 0; 

Selv om jeg nevner det i kommentarene, ville jeg bare påpeke igjen at du ikke vil se den egendefinerte termineringsfunksjonen, med mindre du kjører denne prøven fra en ledetekst. Hvis du kjører det i Visual Studio, vil feilsøkeren fange opp programmet og orkestrere sin egen terminering etter å ha gitt deg en sjanse til å undersøke staten for å se om du kan bestemme hva som gikk galt. Vær også oppmerksom på at dette programmet alltid vil krasje. Dette er av design siden det gir deg mulighet til å se termineringshandleren i aksjon.

Konklusjon

Som vi så i denne artikkelen, hjelper RAII at du frigjør ressurser, uten unntak, ved å bare bruke automatiske lagringsvarighetsobjekter som inneholder ressursene. I neste utgave av denne serien zoomer vi inn på pekere og referanser.

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