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..
RAII fonctionne en raison de trois faits de base.
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.
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.
std :: unique_ptr
Une fonctionLe 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
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
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_ptr
Le 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.
std :: shared_ptr
Une fonctionLe 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
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.
std :: make_shared
Une fonctionle 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 MetroLa 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
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.
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 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é.
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
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
le 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..
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..