C ++ Kort samengevat Resources Acquisition Is Initialization

Wat is RAII?

RAII staat voor "resource acquisition is initialization." RAII is een ontwerppatroon waarbij C ++ -code wordt gebruikt om bronlekken te voorkomen. Bronlekken gebeuren wanneer een bron die door uw programma is verkregen later niet wordt vrijgegeven. Het bekendste voorbeeld is een geheugenlek. Omdat C ++ geen GC heeft zoals C #, moet je oppassen dat dynamisch toegewezen geheugen wordt vrijgegeven. Anders lekt u dat geheugen. Bronlekken kunnen ook leiden tot het onvermogen om een ​​bestand te openen omdat het bestandssysteem denkt dat het al open is, het onvermogen om een ​​vergrendeling te verkrijgen in een multi-threaded programma of het onvermogen om een ​​COM-object vrij te geven.


Hoe werkt RAII?

RAII werkt vanwege drie basisfeiten.

  1. Wanneer een automatisch opslagduurobject buiten bereik raakt, wordt de destructor uitgevoerd.
  2. Wanneer een uitzondering optreedt, worden alle automatische durationobjecten die volledig zijn geconstrueerd sinds het laatste try-blok begon vernietigd in de omgekeerde volgorde waarin ze zijn gemaakt voordat een catch-handler wordt aangeroepen.
  3. Als u try-blocks nest en geen van de catch-handlers van een innerlijk try-blok dit type uitzonderingen verwerkt, wordt de uitzondering doorgegeven aan het externe try-blok. Alle automatische duurobjecten die volledig zijn opgebouwd binnen dat externe try-blok, worden vervolgens vernietigd in omgekeerde aanmaakvolgorde voordat een catch-handler wordt opgeroepen, enzovoort, totdat er iets misloopt of uw programma vastloopt.

RAII helpt ervoor te zorgen dat u bronnen vrijgeeft, zonder uitzonderingen, door eenvoudigweg gebruik te maken van automatische opslagduurobjecten die de bronnen bevatten. Het is vergelijkbaar met de combinatie van de System.IDisposable interface samen met de instructie gebruiken in C #. Zodra de uitvoering het huidige blok verlaat, door middel van een succesvolle uitvoering of een uitzondering, worden de bronnen vrijgegeven.

Als het gaat om uitzonderingen, is een belangrijk onderdeel om te onthouden dat alleen volledig geconstrueerde objecten worden vernietigd. Als u een uitzondering in het midden van een constructor ontvangt en het laatste try-blok buiten die constructor is gestart, omdat het object niet volledig is gebouwd, kan de destructor niet worden uitgevoerd.

Dit betekent niet dat de lidvariabelen, die objecten zijn, niet zullen worden vernietigd. Alle lidvariabele-objecten die volledig binnen de constructor waren geconstrueerd voordat de uitzondering plaatsvond, zijn volledig geconstrueerde automatische durationobjecten. Zodoende zullen die lidobjecten op dezelfde manier worden vernietigd als alle andere volledig geconstrueerde objecten.

Daarom moet je altijd dynamische toewijzingen in beide plaatsen std :: unique_ptr of std :: shared_ptr. Instanties van die typen worden volledig geconstrueerde objecten wanneer de toewijzing slaagt. Zelfs als de constructor voor het object dat je aan het maken bent verder faalt, de std :: unique_ptr middelen zullen worden bevrijd door zijn destructor en de std :: shared_ptr bronnen zullen hun referentietelling laten afnemen en worden vrijgegeven als de telling nul wordt.

RAII gaat natuurlijk niet alleen over shared_ptr en unique_ptr. Het is ook van toepassing op andere resourcetypes, zoals een bestandsobject, waarbij de overname de opening van het bestand is en de destructor ervoor zorgt dat het bestand correct wordt gesloten. Dit is een bijzonder goed voorbeeld, omdat je die code slechts één keer hoeft te maken - wanneer je de les schrijft - in plaats van opnieuw en opnieuw, wat je moet doen als je de sluitlogica schrijft elke plaats die je moet openen het dossier.


Hoe gebruik ik RAII?

RAII-gebruik wordt beschreven door de naam: het verkrijgen van een dynamische bron moet de initialisatie van een object voltooien. Als u dit one-resource-per-object-patroon volgt, is het onmogelijk om een ​​bronlek te krijgen. U kunt de bron met succes aanschaffen, in welk geval het object dat het bevat de constructie voltooit en wordt vernietigd, of de acquisitiepoging mislukt, in welk geval u de bron niet hebt verkregen; er is dus geen middel om vrij te geven.

De destructor van een object dat een resource inkapselt, moet die resource vrijgeven. Dit is, onder andere, een van de belangrijke redenen waarom destructors nooit uitzonderingen mogen maken, behalve die ze vangen en verwerken in zichzelf.

Als de destructor een niet-afgevangen uitzondering gooide, dan, om Bjarne Stroustrup te citeren: "Allerlei slechte dingen zullen waarschijnlijk gebeuren omdat de basisregels van de standaardbibliotheek en de taal zelf worden geschonden. Doe het niet. "

Zoals hij zei, doe het niet. Zorg ervoor dat u weet welke uitzonderingen, als die er zijn, alles wat u in uw destructors roept, zou kunnen werpen, zodat u ervoor kunt zorgen dat u ze goed verwerkt.

Nu denk je misschien dat als je dit patroon volgt, je een hoop lessen zult schrijven. Je zult hier af en toe een extra les schrijven, maar je schrijft waarschijnlijk niet te veel vanwege slimme aanwijzingen. Slimme verwijzingen zijn ook objecten. De meeste typen dynamische bronnen kunnen in ten minste één van de bestaande slimme pointerklassen worden geplaatst. Wanneer u een resource-acquisitie in een geschikte slimme pointer plaatst, als de acquisitie slaagt, zal dat slimme pointer-object volledig worden geconstrueerd. Als er een uitzondering optreedt, wordt de destructor van het slimme aanwijzerobject aangeroepen en wordt de resource vrijgegeven.

Er zijn verschillende belangrijke typen slimme aanwijzers. Laten we ze eens bekijken.

De std :: unique_ptr Functie

De unieke aanwijzer, std :: unique_ptr, is ontworpen om een ​​wijzer vast te houden aan een dynamisch toegewezen object. U moet dit type alleen gebruiken als u een aanwijzer naar het object wilt hebben. Het is een sjabloonklasse die een verplicht en een optioneel sjabloonargument opneemt. Het verplichte argument is het type van de aanwijzer die het zal houden. Bijvoorbeeld auto result = std :: unique_ptr(nieuwe int ()); maakt een unieke aanwijzer die een int * bevat. Het optionele argument is het type deleter. We zien hoe een deleter in een komende sample moet worden geschreven. Meestal kunt u voorkomen dat u een deleter opgeeft, omdat de default_deleter, die voor u is opgegeven als er geen deleter is opgegeven, bijna elke zaak omvat die u zich kunt voorstellen.

Een klasse die dat wel heeft std :: unique_ptr als lidvariabele kan geen standaardkopieerconstructor zijn. Kopie-semantiek is uitgeschakeld voor std :: unique_ptr. Als u een kopie-constructor in een klasse met een unieke aanwijzer wilt, moet u deze schrijven. U moet ook een overbelasting schrijven voor de kopie-operator. Normaal gesproken wil je std :: shared_ptr in dat geval.

U hebt echter misschien zoiets als een reeks gegevens. U wilt misschien ook dat elke kopie van de klasse een kopie van de gegevens maakt zoals die op dat moment bestaat. In dat geval kan een unieke aanwijzer met een aangepaste kopieconstructor de juiste keuze zijn.

std :: unique_ptr is gedefinieerd in de header-bestand.

std :: unique_ptr heeft vier belangrijke ledenfuncties.

De functie get member retourneert de opgeslagen aanwijzer. Als u een functie moet aanroepen waarvoor u de ingesloten aanwijzer moet doorgeven, gebruikt u om een ​​kopie van de aanwijzer op te halen.

De releaselijstfunctie retourneert ook de opgeslagen aanwijzer, maar de release maakt de unique_ptr ongeldig in het proces door de opgeslagen aanwijzer te vervangen door een lege aanwijzer. Als u een functie hebt waarin u een dynamisch object wilt maken en het vervolgens retourneert, terwijl u toch de uitzonderingsveiligheid handhaaft, gebruikt u std: unique_ptr om het dynamisch gemaakte object op te slaan en vervolgens het resultaat van de oproeprelease te retourneren. Dit geeft u een uitzonderingsveiligheid terwijl u het dynamische object kunt retourneren zonder het te vernietigen met de std :: unique_ptrs destructor wanneer het besturingselement de functie verlaat bij het retourneren van de vrijgegeven aanwijzerwaarde aan het einde.

Met de functie Wisselen lid kunnen twee unieke aanwijzers hun opgeslagen aanwijzers uitwisselen, dus als A een aanwijzer naar X houdt en B een aanwijzer naar Y houdt, is het resultaat van bellen A :: swap (B); is dat A nu een wijzer naar Y zal houden, en B een wijzer naar X zal houden. De deleters voor elke zullen ook worden verwisseld, dus als je een aangepaste deleter hebt voor een of beide unieke wijzers, wees er dan van verzekerd dat elke behoud de bijbehorende deleter.

De functie Reset lid zorgt ervoor dat het object waarnaar wordt verwezen door de opgeslagen aanwijzer, indien aanwezig, in de meeste gevallen wordt vernietigd. Als de huidige opgeslagen aanwijzer null is, wordt niets vernietigd. Als u een aanwijzer doorgeeft aan het object waarnaar de huidige opgeslagen pointer wijst, wordt niets vernietigd. U kunt kiezen om een ​​nieuwe pointer in te geven, nullptr, of om de functie zonder parameters aan te roepen. Als u een nieuwe aanwijzer doorgeeft, wordt dat nieuwe object opgeslagen. Als u nullptr doorgeeft, wordt de unieke pointer null opgeslagen. Het aanroepen van de functie zonder parameters is hetzelfde als het aanroepen van nullptr.

De std :: shared_ptr Functie

De gedeelde aanwijzer, std :: shared_ptr, is ontworpen om een ​​aanwijzer te houden naar een dynamisch toegewezen object en om er een referentietelling voor te houden. Het is geen magie; als u twee gedeelde aanwijzers maakt en ze elke aanwijzer doorgeeft aan hetzelfde object, krijgt u uiteindelijk twee gedeelde aanwijzers - elk met een referentietelling van 1, niet 2. De eerste die wordt vernietigd, geeft de onderliggende resource vrij, waardoor catastrofale resultaten wanneer u de andere probeert te gebruiken of wanneer de andere wordt vernietigd en probeert de reeds vrijgegeven onderliggende bron vrij te geven.

Als u de gedeelde aanwijzer op de juiste manier wilt gebruiken, maakt u een exemplaar met een objectaanwijzer en maakt u vervolgens alle andere gedeelde aanwijzers voor dat object van een bestaande geldige gedeelde aanwijzer voor dat object. Dit zorgt voor een gemeenschappelijke referentietelling, zodat de bron een juiste levensduur heeft. Laten we een snel voorbeeld bekijken om de juiste en foute manieren te vinden om shared_ptr-objecten te maken.

Voorbeeld: SharedPtrSample \ SharedPtrSample.cpp

#include  #include  #include  #include "... /pchar.h" met behulp van naamruimte std; struct TwoInts TwoInts (void): A (), B ()  TwoInts (int a, int b): A (a), B (b)  int A; int B; ; wostream & operator<<(wostream& stream, TwoInts* v)  stream << v->EEN << L" " << v->B; retourstroom;  int _pmain (int / * argc * /, _pchar * / * argv * / []) //// Slecht: resultaten in dubbel vrij. // try // // TwoInts * p_i = nieuwe TwoInts (10, 20); // auto sp1 = shared_ptr(pi); // auto sp2 = shared_ptr(pi); // 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(nieuwe 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 is gedefinieerd in de header-bestand.

std :: shared_ptr heeft vijf belangrijke ledenfuncties.

De functie get member werkt op dezelfde manier als de functie std :: unique_ptr :: get member.

De use_count lidfunctie retourneert een lange, die u vertelt wat de huidige referentietelling voor het doelobject is. Dit bevat geen zwakke referenties.

De unieke lidfunctie retourneert een bool en informeert u of deze specifieke gedeelde aanwijzer de enige eigenaar van het doelobject is.

De functie Wisselen lid werkt hetzelfde als de std :: unique_ptr :: swap lidfunctie, met de toevoeging dat de referentietellingen voor de resources hetzelfde blijven.

Met de functie Reset-lid wordt het referentietelling voor de onderliggende resource verlaagd en vernietigd als de resourcetelling nul wordt. Als een aanwijzer naar een object wordt doorgegeven, slaat de gedeelde aanwijzer deze op en begint een nieuw referentietelling voor die aanwijzer. Als nullptr wordt doorgegeven of als er geen parameter wordt doorgegeven, wordt de gedeelde aanwijzer null opgeslagen.

De std :: make_shared Functie

De std :: make_shared sjabloonfunctie is een handige manier om een ​​initiaal te construeren std :: shared_ptr. Zoals we eerder zagen in SharedPtrSample, u geeft het type door als het sjabloonargument en geeft eenvoudig de argumenten, indien aanwezig, door aan de gewenste constructor. std :: make_shared zal een heap-instantie van het objecttype template-argument maken en er een maken std :: shared_ptr. Je kunt dat vervolgens doorgeven std :: shared_ptr als een argument voor de std :: shared_ptr constructor om meer verwijzingen naar dat gedeelde object te maken.


ComPtr in WRL voor Metro-Style Apps

De Windows Runtime Template Library (WRL) biedt een slimme aanwijzer met de naam ComPtr in de Microsoft :: WRL-naamruimte voor gebruik met COM-objecten in Windows 8 Metro-achtige toepassingen. De aanwijzer bevindt zich in de header, als onderdeel van de Windows SDK (minimale versie 8.0).

De meeste functionaliteit van het besturingssysteem die u in toepassingen in Metro-stijl kunt gebruiken, wordt zichtbaar gemaakt door Windows Runtime ("WinRT"). WinRT-objecten bieden hun eigen automatische referentie-telfunctionaliteit voor het maken en vernietigen van objecten. Sommige systeemfunctionaliteit, zoals Direct3D, vereist dat u het rechtstreeks gebruikt en manipuleert via klassieke COM. ComPtr handelt COM's IUnknown-gebaseerde referentietelling voor u af. Het biedt ook handige wrappers voor QueryInterface en bevat andere functies die handig zijn voor slimme aanwijzers.

De twee lidfuncties die u gewoonlijk gebruikt, zijn: Zo verkrijgt u een andere interface voor het onderliggende COM-object en krijgt u een interfacepointer naar het onderliggende COM-object dat de ComPtr bevat (dit is het equivalent van std :: unique_ptr :: krijgen).

Soms gebruik je Detach, dat werkt op dezelfde manier als std :: unique_ptr :: release maar heeft een andere naam omdat release in COM impliceert dat het referentietelling wordt verlaagd en Detach doet dat niet.

U kunt ReleaseAndGetAddressOf gebruiken voor situaties waarin u een bestaande ComPtr hebt die al een COM-object zou kunnen bevatten en u deze wilt vervangen door een nieuw COM-object van hetzelfde type. ReleaseAndGetAddressOf doet hetzelfde als de lidfunctie van GetAddressOf, maar geeft eerst de onderliggende interface vrij, indien aanwezig.


Uitzonderingen in C++

In tegenstelling tot .NET, waar alle uitzonderingen voortvloeien uit System.Exception en gegarandeerde methoden en eigenschappen hebben, zijn C ++ -uitzonderingen niet vereist om van iets af te leiden; ook hoeven ze geen klassen te zijn. In C ++ gooi je L "Hello World!"; is perfect acceptabel voor de compiler, net als throw 5 ;. Kortom, uitzonderingen kunnen van alles zijn.

Dat gezegd hebbende, veel C ++ -programmeurs zullen niet blij zijn om een ​​uitzondering te zien die niet afgeleid is std :: uitzondering (gevonden in de header). Afleidende alle uitzonderingen van std :: uitzondering biedt een manier om uitzonderingen van het onbekende type op te vangen en informatie erover op te halen via de functie wat lid voordat ze opnieuw worden gegooid. std :: uitzondering :: wat neemt geen parameters op en geeft a const char * string, die u kunt bekijken of loggen, zodat u weet wat de oorzaak van de uitzondering is.

Er is geen stacktracering - nog afgezien van de stack-trace-mogelijkheden die uw debugger biedt - met C ++ -uitzonderingen. Omdat automatische durationobjecten binnen het bereik van het try-blok dat de uitzondering opvangt automatisch worden vernietigd voordat de toepasselijke catch-handler, indien aanwezig, is geactiveerd, hebt u niet de luxe om de gegevens te onderzoeken die mogelijk de uitzondering hebben veroorzaakt. Het enige waaraan u in eerste instantie moet werken, is het bericht van de functie welk lid.

Als het gemakkelijk is om de voorwaarden te creëren die tot de uitzondering hebben geleid, kunt u een onderbrekingspunt instellen en het programma opnieuw uitvoeren, zodat u de uitvoering van het probleemgebied kunt doorlopen en mogelijk het probleem kunt herkennen. Omdat dat niet altijd mogelijk is, is het belangrijk om zo precies mogelijk te zijn met de foutmelding.

Bij het afleiden van std :: uitzondering, u moet ervoor zorgen dat u de functie van het lid opheft om een ​​nuttige foutmelding te geven die u en andere ontwikkelaars helpt te achterhalen wat er mis is gegaan.

Sommige programmeurs gebruiken een variant van een regel die bepaalt dat je altijd moet gooien std :: uitzondering-afgeleide uitzonderingen. Onthoud dat het startpunt (main of wmain) een geheel getal retourneert, deze programmeurs zullen gooien std :: uitzondering-afgeleide uitzonderingen wanneer hun code kan worden hersteld, maar zal gewoon een goed gedefinieerde integerwaarde genereren als de fout niet kan worden hersteld. De startpuntcode wordt ingepakt in een try-blok met een catch voor een int. De catch-handler retourneert de gevangen int-waarde. Op de meeste systemen betekent een retourwaarde van 0 uit een programma succes. Elke andere waarde betekent mislukking.

Als er een catastrofale storing is, kan het gooien van een goed gedefinieerde geheel getal anders dan 0 een bijdrage leveren aan het geven van enige betekenis. Tenzij je aan een project werkt waar dit de gewenste stijl is, moet je je houden aan std :: uitzondering-afgeleide uitzonderingen, omdat ze programma's met uitzonderingen laten werken met behulp van een eenvoudig logboeksysteem om berichten op te nemen van uitzonderingen die niet worden afgehandeld, en ze voeren elke opruiming uit die veilig is. Iets gooien waar niet uit voortvloeit std :: uitzondering zou interfereren met deze foutregistratiemechanismen.

Een laatste ding om op te merken is dat de constructie van C # uiteindelijk geen equivalent heeft in C ++. Het juiste RAI-idioom maakt het overbodig omdat alles is opgeschoond.


C ++ Standaardbibliotheekuitzonderingen

We hebben het al besproken std :: uitzondering, maar er zijn meer typen dan die beschikbaar zijn in de standaardbibliotheek, en er is extra functionaliteit om te verkennen. Laten we eens kijken naar de functionaliteit van de headerbestand eerst.

De std :: beëindigen functie, laat je standaard crashen vanuit elke applicatie. Het zou spaarzaam moeten worden gebruikt, omdat het niet door een uitzondering te noemen, alle normale uitzonderingsafhandelingsmechanismen zal omzeilen. Als u wilt, kunt u een aangepaste afsluitfunctie schrijven zonder parameters en retourwaarden. Een voorbeeld hiervan is te zien in ExceptionsSample, dat eraan komt.

Om de aangepaste terminate in te stellen, belt u std :: set_terminate en geef het het adres van de functie door. U kunt de aangepaste afsluiter op elk moment wijzigen; de laatste functieset is wat wordt gebeld in het geval van een oproep naar std :: beëindigen of een niet-afgehandelde uitzondering. De standaard handler roept de functie Afbreken op uit de header-bestand.

De header biedt een rudimentair raamwerk voor uitzonderingen. Het definieert twee klassen die erft van std :: uitzondering. Die twee klassen dienen als de ouderklasse voor verschillende andere klassen.

De std :: runtime_error class is de bovenliggende klasse voor uitzonderingen die worden gegenereerd door de runtime of als gevolg van een fout in een standaardbibliotheekfunctie van C ++. Haar kinderen zijn de std :: overflow_error klas, de std :: range_error klasse en de std :: underflow_error klasse.

De std :: logic_error class is de bovenliggende klasse voor uitzonderingen die zijn gegenereerd vanwege programmeerfout. Haar kinderen zijn de std :: domain_error klas, de std :: invalid_argument klas, de std :: length_error klasse en de std :: out_of_range klasse.

U kunt deze klassen afleiden of uw eigen uitzonderingsklassen maken. Het bedenken van een goede uitzonderingshiërarchie is een moeilijke taak. Aan de ene kant wilt u uitzonderingen die specifiek genoeg zijn om alle uitzonderingen op basis van uw kennis tijdens de build-tijd af te handelen. Aan de andere kant wilt u geen uitzonderingsklasse voor elke fout die kan optreden. Uw code zou opgeblazen en log zijn, om nog maar te zwijgen van de verspilling van tijd bij het schrijven van catch-handlers voor elke uitzonderingsklasse.

Besteed tijd aan een whiteboard, of met een pen en papier, of hoe je ook wilt nadenken over de uitzonderingsboom die je applicatie zou moeten hebben.

Het volgende voorbeeld bevat een klasse genaamd InvalidArgumentExceptionBase, die wordt gebruikt als het bovenliggende element van een opgeroepen sjabloonklasse InvalidArgumentException. De combinatie van een basisklasse, die kan worden gevangen met één uitzonderingshandler, en een sjabloonklasse, waarmee we de uitvoerdiagnose kunnen aanpassen op basis van het type parameter, is een optie voor het balanceren tussen specialisatie en code-bloat.

De sjabloonklasse lijkt op dit moment misschien verwarrend; we zullen sjablonen bespreken in een volgend hoofdstuk, op welk punt dingen die momenteel onduidelijk zijn, moeten ophelderen.

Voorbeeld: ExceptionsSample \ InvalidArgumentException.h

#pragma één keer # opnemen  #include  #include  #include  naamruimte CppForCsExceptions class InvalidArgumentExceptionBase: public std :: invalid_argument public: InvalidArgumentExceptionBase (void): std :: invalid_argument ("")  virtual ~ InvalidArgumentExceptionBase (void) throw ()  virtual const char * what (void) const throw () override = 0; ; sjabloon  class InvalidArgumentException: public InvalidArgumentExceptionBase public: inline InvalidArgumentException (const char * className, const char * functionSignature, const char * parameterName, T parameterValue); inline virtueel ~ InvalidArgumentException (void) throw (); inline virtuele const char * wat (ongeldig) const throw () override; private: std :: string m_whatMessage; ; sjabloon InvalidArgumentException:: InvalidArgumentException (const char * className, const char * functionSignature, const char * parameterName, 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) throw ()  sjabloon const char * InvalidArgumentException:: what (void) const throw () return m_whatMessage.c_str (); 

Voorbeeld: uitzonderingenSample \ ExceptionsSample.cpp

#include  #include  #include  #include  #include  #include  #include  #include  #include "InvalidArgumentException.h" # include "... /pchar.h" met behulp van de naamruimte CppForCsExceptions; namespace std; gebruiken; class 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 ), zal het programma // abnormaal afsluiten. Het zal ook abnormaal afsluiten als u // anything niet roept om te zorgen dat het deze methode verlaat. abort (); //// Als u in plaats daarvan exit (0) (ook vanuit ), //// dan zou je programma afsluiten, alsof er niets was misgegaan. Dit is slecht omdat er iets fout is gegaan. //// Ik presenteer dit zodat je weet dat het mogelijk is voor //// een programma om een ​​niet-afgevangen uitzondering te gooien en nog steeds //// exit op een manier die niet geïnterpreteerd wordt als een crash, omdat //// het kan nodig zijn om uit te zoeken waarom een ​​programma abrupt blijft //// het programma verlaat maar niet crasht. Dit zou hiervoor een oorzaak //// zijn. // exit (0);  int _pmain (int / * argc * /, _pchar * / * argv * / []) // Stel een aangepaste handler in voor std :: terminate. Merk op dat deze handler // niet zal worden uitgevoerd tenzij u deze uitvoert vanaf een opdrachtprompt. De foutopsporing // onderschept de niet-verwerkte uitzondering en biedt // foutopsporingsopties wanneer u deze uitvoert vanuit Visual Studio. set_terminate (& TerminateHandler); probeer 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. Nu kunnen we ze allemaal vangen, indien gewenst, // zonder dat er een catch-handler voor nodig is. Als je echter wilt, // kun je nog steeds een handler hebben voor een bepaalde specialisatie. catch (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; 

Hoewel ik het in de opmerkingen vermeldde, wilde ik er alleen nog op wijzen dat je de aangepaste afsluitfunctie niet zult zien tenzij je dit voorbeeld van een opdrachtprompt uitvoert. Als u het in Visual Studio uitvoert, onderschept het foutopsporingsprogramma het programma en orkestreert het zijn eigen beëindiging nadat u de gelegenheid hebt gegeven om de staat te onderzoeken om te zien of u kunt bepalen wat er mis ging. Merk ook op dat dit programma altijd crasht. Dit is door het ontwerp, omdat u hiermee de terminale afhandelingsroutine in actie kunt zien.

Conclusie

Zoals we in dit artikel hebben gezien, helpt RAII ervoor zorgen dat u bronnen vrijgeeft, zonder uitzonderingen, door eenvoudigweg automatische opslagduurobjecten te gebruiken die de bronnen bevatten. In de volgende aflevering van deze serie zoomen we in op wijzers en verwijzingen.

Deze les staat voor een hoofdstuk uit C ++ Kort gezegd, een gratis eBoek van het team van Syncfusion.