C ++ succinctement l'acquisition des ressources est une initialisation

Qu'est-ce que RAII??

RAII signifie «l'acquisition des ressources est une initialisation». RAII est un modèle de conception utilisant le code C ++ pour éliminer les fuites de ressources. Les fuites de ressources se produisent lorsqu'une ressource acquise par votre programme n'est pas publiée par la suite. L'exemple le plus familier est une fuite de mémoire. Comme C ++ n’a pas de GC comme C #, vous devez veiller à ce que la mémoire allouée dynamiquement soit libérée. Sinon, vous perdrez cette mémoire. Les fuites de ressources peuvent également entraîner l'impossibilité d'ouvrir un fichier, car le système de fichiers pense qu'il est déjà ouvert, l'impossibilité d'obtenir un verrou dans un programme multithread ou l'impossibilité de libérer un objet COM..


Comment fonctionne le RAII?

RAII fonctionne en raison de trois faits de base.

  1. Lorsqu'un objet de durée de stockage automatique sort du cadre, son destructeur s'exécute.
  2. Lorsqu'une exception se produit, tous les objets de durée automatique entièrement construits depuis le début du dernier try-block sont détruits dans l'ordre inverse de leur création avant l'appel de tout gestionnaire de capture..
  3. Si vous imbriquez try-block, et qu'aucun des gestionnaires d'interception d'un try-block interne ne gère ce type d'exception, l'exception se propage au bloc try-block externe. Tous les objets de durée automatique qui ont été entièrement construits dans ce bloc try externe sont ensuite détruits dans l'ordre de création inverse avant qu'un gestionnaire de capture ne soit appelé, et ainsi de suite, jusqu'à ce que quelque chose intercepte l'exception ou que votre programme se bloque..

RAII vous aide à libérer les ressources, sans exception, en utilisant simplement des objets de durée de stockage automatique qui contiennent les ressources. Il est similaire à la combinaison de la System.IDisposable interface avec l'instruction using en C #. Une fois que l'exécution quitte le bloc actuel, que ce soit par le biais d'une exécution réussie ou d'une exception, les ressources sont libérées..

En ce qui concerne les exceptions, un élément clé à retenir est que seuls les objets entièrement construits sont détruits. Si vous recevez une exception au milieu d'un constructeur et que le dernier bloc try a commencé en dehors de ce constructeur, l'objet n'étant pas entièrement construit, son destructeur ne s'exécutera pas..

Cela ne signifie pas que ses variables membres, qui sont des objets, ne seront pas détruites. Tous les objets de variable membre entièrement construits dans le constructeur avant la survenue de l'exception sont des objets de durée automatique entièrement construits. Ainsi, ces objets membres seront détruits de la même manière que tout autre objet entièrement construit..

C’est pourquoi vous devez toujours mettre des allocations dynamiques à l’intérieur de std :: unique_ptr ou std :: shared_ptr. Les instances de ces types deviennent des objets entièrement construits lorsque l'allocation aboutit. Même si le constructeur de l’objet que vous créez échoue plus loin, le std :: unique_ptr les ressources seront libérées par son destructeur et la std :: shared_ptr les ressources auront leur compte de référence décrémenté et seront libérées si le compte devient zéro.

RAII ne concerne bien sûr pas shared_ptr et unique_ptr. Cela s'applique également à d'autres types de ressources, tels qu'un objet fichier, où l'acquisition est l'ouverture du fichier et le destructeur veille à ce que le fichier soit correctement fermé. C’est un exemple particulièrement intéressant, car vous n’avez besoin que de créer ce code juste une fois, lorsque vous écrivez la classe, et non encore et encore. C’est ce que vous devez faire si vous écrivez la logique de fermeture à chaque endroit où vous devez ouvrir un fichier. fichier.


Comment utiliser RAII?

L'utilisation de RAII est décrite par son nom: L'acquisition d'une ressource dynamique doit compléter l'initialisation d'un objet. Si vous suivez ce modèle d'une ressource par objet, il est impossible de vous retrouver avec une fuite de ressources. Soit vous réussissez à acquérir la ressource, auquel cas l'objet qui l'encapsule terminera la construction et sera sujet à destruction, ou la tentative d'acquisition échouera, auquel cas vous n'acquerrez pas la ressource; ainsi, il n'y a aucune ressource à libérer.

Le destructeur d'un objet qui encapsule une ressource doit libérer cette ressource. C'est entre autres l'une des raisons importantes pour lesquelles les destructeurs ne devraient jamais lancer d'exceptions, à l'exception de ceux qu'ils interceptent et manipulent eux-mêmes..

Si le destructeur lançait une exception non interceptée, alors, pour citer Bjarne Stroustrup, «toutes sortes de mauvaises choses risquent de se produire, car les règles de base de la bibliothèque standard et du langage lui-même seront violés. Ne le fais pas. "

Comme il l'a dit, ne le fais pas. Assurez-vous de savoir quelles exceptions, le cas échéant, tout ce que vous appelez dans vos destructeurs peut lancer afin de vous assurer de les gérer correctement..

Maintenant, vous pensez peut-être que si vous suivez ce modèle, vous finirez par écrire une tonne de cours. Vous aurez parfois l'occasion d'écrire une classe supplémentaire ici et là, mais vous ne risquez pas d'en écrire trop à cause des indicateurs intelligents. Les pointeurs intelligents sont aussi des objets. La plupart des types de ressources dynamiques peuvent être placés dans au moins une des classes de pointeur intelligent existantes. Lorsque vous placez une acquisition de ressources dans un pointeur intelligent approprié, si l'acquisition réussit, cet objet pointeur intelligent est entièrement construit. Si une exception se produit, le destructeur de l'objet pointeur intelligent sera appelé et la ressource sera libérée..

Il existe plusieurs types de pointeurs intelligents importants. Regardons-les.

le std :: unique_ptr Une fonction

Le pointeur unique, std :: unique_ptr, est conçu pour contenir un pointeur sur un objet alloué dynamiquement. Vous ne devez utiliser ce type que si vous souhaitez qu'un seul pointeur sur l'objet existe. C'est une classe de modèle qui prend un argument obligatoire et un argument facultatif. L'argument obligatoire est le type du pointeur qu'il contiendra. Par exemple résultat automatique = std :: unique_ptr(new int ()); créera un pointeur unique contenant un int *. L'argument optionnel est le type de deleter. Nous voyons comment écrire un deleter dans un prochain échantillon. En règle générale, vous pouvez éviter de spécifier un deleter car le default_deleter, qui vous est fourni si aucun deleter n'est spécifié, couvre presque tous les cas que vous pouvez imaginer..

Une classe qui a std :: unique_ptr Une variable membre ne peut pas avoir de constructeur de copie par défaut. La sémantique de copie est désactivée pour std :: unique_ptr. Si vous voulez un constructeur de copie dans une classe qui a un pointeur unique, vous devez l'écrire. Vous devriez également écrire une surcharge pour l'opérateur de copie. Normalement, vous voulez std :: shared_ptr dans ce cas.

Cependant, vous pourriez avoir quelque chose comme un tableau de données. Vous pouvez également souhaiter qu'une copie de la classe crée une copie des données telles qu'elles existent à ce moment-là. Dans ce cas, un pointeur unique avec un constructeur de copie personnalisé pourrait être le bon choix.

std :: unique_ptr est défini dans le En tête de fichier.

std :: unique_ptr a quatre fonctions d'intérêt membres.

La fonction get member renvoie le pointeur stocké. Si vous devez appeler une fonction à laquelle vous devez passer le pointeur contenu, utilisez get pour récupérer une copie du pointeur..

La fonction release membre renvoie également le pointeur stocké, mais release invalide le paramètre unique_ptr du processus en remplaçant le pointeur stocké par un pointeur null. Si vous souhaitez créer un objet dynamique, puis le renvoyer, tout en maintenant la sécurité des exceptions, utilisez la commande std: unique_ptr pour stocker l’objet créé dynamiquement, puis renvoyer le résultat de l’appel de libération. Cela vous donne une sécurité d’exception tout en vous permettant de retourner l’objet dynamique sans le détruire avec le std :: unique_ptrLe destructeur de lorsque le contrôle quitte la fonction en renvoyant la valeur du pointeur libérée à la fin.

La fonction swap member permet à deux pointeurs uniques d’échanger leurs pointeurs stockés. Ainsi, si A tient un pointeur sur X et que B tient un pointeur sur Y, le résultat de l’appel A :: échange (B); est que A tiendra maintenant un pointeur sur Y, et B tiendra un pointeur sur X. Les deleters de chacun seront également échangés. Ainsi, si vous avez un deleter personnalisé pour l'un ou les deux pointeurs uniques, assurez-vous que chacun conserve son deleter associé.

La fonction de réinitialisation du membre entraîne la destruction de l'objet pointé par le pointeur stocké, le cas échéant, dans la plupart des cas. Si le pointeur stocké actuel est null, rien n'est détruit. Si vous passez un pointeur sur l'objet pointé par le pointeur stocké actuel, rien n'est détruit. Vous pouvez choisir de passer un nouveau pointeur, nullptr, ou d'appeler la fonction sans paramètre. Si vous passez un nouveau pointeur, ce nouvel objet est stocké. Si vous transmettez nullptr, le pointeur unique stockera null. Appeler la fonction sans paramètre revient à l'appeler avec nullptr.

le std :: shared_ptr Une fonction

Le pointeur partagé, std :: shared_ptr, est conçu pour contenir un pointeur sur un objet alloué dynamiquement et conserver un compte de références pour celui-ci. Ce n'est pas de la magie; Si vous créez deux pointeurs partagés et que vous leur transmettez chacun un pointeur vers le même objet, vous obtiendrez deux pointeurs partagés, chacun avec un nombre de références égal à 1, et non 2. Le premier détruit libérera la ressource sous-jacente, ce qui donnera: résultats catastrophiques lorsque vous essayez d'utiliser l'autre ou lorsque l'autre est détruit et essayez de libérer la ressource sous-jacente déjà publiée.

Pour utiliser correctement le pointeur partagé, créez une instance avec un pointeur d'objet, puis créez tous les autres pointeurs partagés pour cet objet à partir d'un pointeur partagé existant valide pour cet objet. Cela garantit un nombre de références commun afin que la ressource ait une durée de vie appropriée. Regardons un exemple rapide pour voir les bonnes et les mauvaises façons de créer des objets shared_ptr.

Exemple: SharedPtrSample \ SharedPtrSample.cpp

#comprendre  #comprendre  #comprendre  #include "… /pchar.h" en utilisant namespace std; struct TwoInts TwoInts (void): A (), B ()  TwoInts (int a, int b): A (a), B (b)  int A; int B; ; wostream & opérateur<<(wostream& stream, TwoInts* v)  stream << v->UNE << L" " << v->B; flux de retour;  int _pmain (int / * argc * /, _pchar * / * argv * / []) //// Mauvais: donne un double libre. // essayer // // TwoInts * p_i = new 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(new 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 est défini dans le En tête de fichier.

std :: shared_ptr a cinq fonctions d'intérêt membres.

La fonction get member fonctionne de la même manière que la fonction std :: unique_ptr :: get member.

La fonction membre use_count renvoie un long qui vous indique le nombre actuel de références pour l'objet cible. Cela n'inclut pas les références faibles.

La fonction membre unique renvoie une valeur booléenne, vous indiquant si ce pointeur partagé particulier est l'unique propriétaire de l'objet cible..

La fonction membre swap fonctionne de la même manière que std :: unique_ptr :: swap fonction membre, avec l'ajout que la référence compte pour les ressources reste la même.

La fonction de membre de réinitialisation décrémente le nombre de références pour la ressource sous-jacente et la détruit si le nombre de ressources devient nul. Si un pointeur sur un objet est passé, le pointeur partagé le stocke et commence un nouveau décompte de références pour ce pointeur. Si nullptr est passé, ou si aucun paramètre n'est passé, le pointeur partagé stockera null.

le std :: make_shared Une fonction

le std :: make_shared fonction de modèle est un moyen pratique de construire une première std :: shared_ptr. Comme nous l'avons vu précédemment dans SharedPtrSample, vous transmettez le type en tant qu'argument de modèle, puis vous transmettez simplement les arguments éventuels du constructeur souhaité. std :: make_shared va construire une instance de tas du type d'objet argument de modèle et en faire une std :: shared_ptr. Vous pouvez alors passer ça std :: shared_ptr comme argument à la std :: shared_ptr constructeur pour créer plus de références à cet objet partagé.


ComPtr en WRL pour les applications de style Metro

La bibliothèque de modèles d'exécution Windows (WRL) fournit un pointeur intelligent nommé ComPtr dans l'espace de noms Microsoft :: WRL à utiliser avec les objets COM dans les applications de style métro Windows 8. Le pointeur se trouve dans le en-tête, dans le cadre du SDK Windows (version minimale 8.0).

La plupart des fonctionnalités du système d'exploitation que vous pouvez utiliser dans les applications de style Metro sont exposées par Windows Runtime («WinRT»). Les objets WinRT fournissent leur propre fonctionnalité de comptage automatique des références pour la création et la destruction d'objets. Certaines fonctionnalités du système, telles que Direct3D, vous obligent à les utiliser et à les manipuler directement via COM classique. ComPtr gère pour vous le décompte de références basé sur IUnknown de COM. Il fournit également des wrappers pratiques pour QueryInterface et inclut d'autres fonctionnalités utiles pour les pointeurs intelligents..

Les deux fonctions membres que vous utilisez généralement sont As pour obtenir une interface différente pour l'objet COM sous-jacent et Get pour prendre un pointeur d'interface vers l'objet COM sous-jacent que contient la ComPtr (c'est l'équivalent de std :: unique_ptr :: get).

Parfois, vous utiliserez Detach, qui fonctionne de la même manière que std :: unique_ptr :: release mais dont le nom est différent, car release dans COM implique la décrémentation du nombre de références et que Detach ne le fait pas..

Vous pouvez utiliser ReleaseAndGetAddressOf dans les cas où vous avez un ComPtr existant pouvant déjà contenir un objet COM et que vous souhaitez le remplacer par un nouvel objet COM du même type.. ReleaseAndGetAddressOf fait la même chose que la fonction membre GetAddressOf, mais commence par publier son interface sous-jacente, le cas échéant.


Exceptions en C++

Contrairement à .NET, où toutes les exceptions dérivent de System.Exception et ont des méthodes et des propriétés garanties, les exceptions C ++ ne doivent dériver de rien; ils ne sont même pas obligés d'être des types de classe. En C ++, lancez L "Hello World!"; est parfaitement acceptable pour le compilateur, comme le lance 5 ;. Fondamentalement, les exceptions peuvent être n'importe quoi.

Cela dit, beaucoup de programmeurs C ++ seront mécontents de voir une exception qui ne dérive pas de std :: exception (trouvé dans le entête). Dériver toutes les exceptions de std :: exception fournit un moyen d'attraper les exceptions de type inconnu et de récupérer leurs informations via la fonction member avant de les rediffuser. std :: exception :: quoi ne prend aucun paramètre et retourne un const char * string, que vous pouvez visualiser ou vous connecter afin de savoir la cause de l'exception.

Il n'y a pas de trace de pile - sans compter les capacités de trace de pile fournies par votre débogueur - avec des exceptions C ++. Étant donné que les objets de durée automatique compris dans le bloc try-block qui capture l'exception sont détruits automatiquement avant que le gestionnaire d'attrape approprié, le cas échéant, ne soit activé, vous n'avez pas le loisir d'examiner les données pouvant avoir provoqué l'exception. Au départ, tout ce que vous avez à faire est le message de la fonction what member.

S'il est facile de recréer les conditions qui ont conduit à l'exception, vous pouvez définir un point d'arrêt et réexécuter le programme, ce qui vous permet d'exécuter l'exécution de la zone de problèmes et éventuellement de détecter le problème. Parce que ce n'est pas toujours possible, il est important d'être aussi précis que possible avec le message d'erreur.

En dérivant de std :: exception, vous devez vous assurer de remplacer la fonction what member pour fournir un message d'erreur utile qui vous aidera, ainsi que les autres développeurs, à diagnostiquer les dysfonctionnements.

Certains programmeurs utilisent une variante d’une règle stipulant que vous devez toujours lancer std :: exception-exceptions dérivées. En se rappelant que le point d’entrée (main ou wmain) renvoie un entier, ces programmeurs jetteront std :: exception-les exceptions dérivées lorsque leur code peut récupérer, mais générera simplement une valeur entière bien définie si l'échec est irrécupérable. Le code de point d’entrée sera encapsulé dans un bloc d’essai qui a un attrape pour un int. Le gestionnaire de capture retournera la valeur int capturée. Sur la plupart des systèmes, une valeur de retour de 0 à partir d'un programme signifie succès. Toute autre valeur signifie un échec.

S'il y a une défaillance catastrophique, le lancement d'une valeur entière bien définie autre que 0 peut aider à donner un sens. Sauf si vous travaillez sur un projet où ce style est préféré, vous devez vous en tenir à std :: exception-les exceptions dérivées, puisqu'elles permettent aux programmes de gérer les exceptions à l'aide d'un système de journalisation simple pour enregistrer les messages des exceptions non gérées, et ils effectuent tout nettoyage en toute sécurité. Lancer quelque chose qui ne provient pas de std :: exception interférerait avec ces mécanismes de journalisation des erreurs.

Une dernière chose à noter est que la construction finale de C # n’a pas d’équivalent en C ++. L'idiome RAII, lorsqu'il est correctement implémenté, le rend inutile puisque tout aura été nettoyé.


Exceptions de la bibliothèque standard C ++

Nous avons déjà discuté std :: exception, mais il y a plus de types que ceux disponibles dans la bibliothèque standard, et il y a des fonctionnalités supplémentaires à explorer. Regardons les fonctionnalités du fichier d'en-tête en premier.

le std :: terminate fonction, par défaut, vous permet de sortir de n'importe quelle application. Il doit être utilisé avec parcimonie, car l'appeler plutôt que de lancer une exception contournera tous les mécanismes normaux de traitement des exceptions. Si vous le souhaitez, vous pouvez écrire une fonction de terminaison personnalisée sans paramètre ni valeur de retour. Un exemple de ceci sera vu dans ExceptionsSample, qui arrive.

Pour définir la terminaison personnalisée, vous appelez std :: set_terminate et transmettez-lui l'adresse de la fonction. Vous pouvez modifier le gestionnaire de terminaison personnalisé à tout moment. le dernier ensemble de fonctions est ce qui sera appelé dans le cas d'un appel à std :: terminate ou une exception non gérée. Le gestionnaire par défaut appelle la fonction d'abandon à partir du En tête de fichier.

le header fournit un cadre rudimentaire pour les exceptions. Il définit deux classes qui héritent de std :: exception. Ces deux classes servent de classe parente pour plusieurs autres classes.

le std :: runtime_error class est la classe parente pour les exceptions émises par le moteur d'exécution ou en raison d'une erreur dans une fonction de bibliothèque standard C ++. Ses enfants sont les std :: overflow_error classe, la std :: range_error classe et std :: underflow_error classe.

le std :: logic_error classe est la classe parente pour les exceptions levées à cause d'une erreur du programmeur. Ses enfants sont les std :: domain_error classe, la std :: invalid_argument classe, la std :: length_error classe et std :: out_of_range classe.

Vous pouvez dériver de ces classes ou créer vos propres classes d'exception. Venir avec une bonne hiérarchie des exceptions est une tâche difficile. D'une part, vous souhaitez que les exceptions soient suffisamment spécifiques pour pouvoir gérer toutes les exceptions en fonction de vos connaissances lors de la construction. D'autre part, vous ne voulez pas de classe d'exception pour chaque erreur susceptible de se produire. Votre code finirait par être gonflé et difficile à manier, sans parler de la perte de temps à écrire des gestionnaires de capture pour chaque classe d'exception.

Passez du temps sur un tableau blanc, avec un stylo et du papier ou comme vous voulez en pensant à l'arbre d'exception que votre application devrait avoir.

L'exemple suivant contient une classe appelée InvalidArgumentExceptionBase, qui est utilisé comme parent d'une classe de modèle appelée InvalidArgumentException. La combinaison d'une classe de base, qui peut être interceptée avec un seul gestionnaire d'exception, et d'une classe de modèle, qui nous permet de personnaliser les diagnostics de sortie en fonction du type du paramètre, est une option pour équilibrer la spécialisation et la saturation de code..

La classe de template peut sembler déroutante pour le moment; nous discuterons des modèles dans un chapitre à venir. À ce stade, tout élément incertain pour le moment devrait être éclairci..

Exemple: ExceptionsSample \ InvalidArgumentException.h

#pragma fois #include  #comprendre  #comprendre  #comprendre  espace de noms CppForCsExceptions class InvalidArgumentExceptionBase: public std :: invalid_argument public: InvalidArgumentExceptionBase (void): std :: invalid_argument (")  virtuel ~ InvalidArgumentExceptionBase (void) renvoie ()  virtual caractère (void) () override = 0; ; modèle  class InvalidArgumentException: public InvalidArgumentExceptionBase public: inline InvalidArgumentException (const char * nomClasse, const char * fonctionSignature, const char * paramètreName, T paramètreValeur); inline virtual ~ InvalidArgumentException (void) throw (); char virtuel intégré * quoi (void) const throw () override; private: std :: string m_whatMessage; ; modèle InvalidArgumentException:: InvalidArgumentException (const char * className, const char * functionSignature, const char * paramName, T paramètreValue): 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 ()  template const char * InvalidArgumentException:: what (void) const throw () return m_whatMessage.c_str (); 

Exemple: ExceptionsSample \ ExceptionsSample.cpp

#comprendre  #comprendre  #comprendre  #comprendre  #comprendre  #comprendre  #comprendre  #comprendre  #include "InvalidArgumentException.h" #include "… /pchar.h" en utilisant l'espace de noms CppForCsExceptions; using namespace std; classe 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 ), le programme se terminera // anormalement. Il se fermera également de manière anormale si vous n'appelez rien // pour le faire quitter cette méthode. avorter(); //// Si vous deviez plutôt appeler exit (0) (également à partir de ), //// alors votre programme se fermerait comme si rien ne s'était passé ////. C'est mauvais parce que quelque chose s'est mal passé. //// Je présente ceci pour que vous sachiez qu'il est possible pour //// un programme de lancer une exception non capturée tout en conservant la sortie //// d'une manière qui n'est pas interprétée comme un blocage, car vous devrez peut-être savoir pourquoi un programme arrête brusquement de rester //// sans pour autant être bloqué. Ce serait une telle cause //// pour cela. // exit (0);  int _pmain (int / * argc * /, _pchar * / * argv * / []) // Définit un gestionnaire personnalisé pour std :: terminate. Notez que ce gestionnaire // ne s'exécutera que si vous l'exécutez à partir d'une invite de commande. Le débogueur // interceptera l'exception non gérée et vous présentera // les options de débogage lorsque vous l'exécuterez à partir de Visual Studio. set_terminate (& TerminateHandler); try 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. Maintenant, nous pouvons les attraper tous, si vous le souhaitez, // sans avoir besoin d'un gestionnaire d'attrape pour chacun. Cependant, si vous le souhaitiez, vous pourriez toujours avoir un gestionnaire pour une spécialisation particulière. 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; 

Bien que je le mentionne dans les commentaires, je voulais simplement souligner à nouveau que la fonction de terminaison personnalisée ne sera pas exécutée à moins que vous ne lanciez cet exemple à partir d'une invite de commande. Si vous l'exécutez dans Visual Studio, le débogueur interceptera le programme et orchestrera sa propre terminaison après vous avoir donné l'occasion d'examiner l'état pour voir si vous pouvez déterminer ce qui ne va pas. En outre, notez que ce programme plantera toujours. Ceci est voulu par la conception, car il vous permet de voir le gestionnaire de terminaison en action..

Conclusion

Comme nous l'avons vu dans cet article, RAII vous aide à libérer les ressources, sans exception, en utilisant simplement des objets de durée de stockage automatique contenant les ressources. Dans le prochain épisode de cette série, nous zoomons sur les pointeurs et les références..

Cette leçon représente un chapitre de C ++ Succinctly, un eBook gratuit de l’équipe de Syncfusion..