51 votes

RAII vs exceptions

Plus nous utilisons RAII en C++, plus nous nous trouvons avec les destructeurs que ne le font les non-trivial de libération de la mémoire. Maintenant, de libération de la mémoire (mise au point, cependant vous voulez l'appeler) peut échouer, dans ce cas, des exceptions sont vraiment la seule façon de laisser quelqu'un à l'étage au courant de notre problème de libération de la mémoire. Mais là encore, lancer-les destructeurs sont une mauvaise idée à cause de la possibilité de l'exception levée pendant le déroulement de pile. std::uncaught_exception() vous permet de savoir quand cela se passe, mais pas beaucoup plus, donc, en plus de vous permettre de connecter un message avant la fin il n'y a pas beaucoup que vous pouvez faire, sauf si vous êtes prêt à quitter votre programme dans un état indéfini, où certains trucs est désalloué/finalisé et d'autres non.

Une approche est de ne pas jeter des destructeurs. Mais dans de nombreux cas, qui vient se cache une véritable erreur. Notre destructeur pourrait, par exemple, la fermeture de certains RAII-géré DB connections comme un résultat de certains exception levée, et ceux DB connexions échouent à les fermer. Cela ne veut pas nécessairement dire que nous sommes ok avec le programme de s'arrêter à ce point. D'autre part, de journalisation et de suivi de ces erreurs n'est pas vraiment une solution pour chaque cas; sinon, on n'aurait pas eu besoin d'exceptions pour commencer. Sans jeter destructeurs aussi, nous trouvons dans l'obligation de créer des "reset()" les fonctions qui sont censés être appelé avant la destruction - mais qui l'emporte sur tout le but entier de RAII.

Une autre approche consiste simplement à laisser le programme de résilier, comme c'est le plus prévisible chose que vous pouvez faire.

Certaines personnes suggèrent de chaînage des exceptions, de sorte que plus d'une erreur peuvent être traitées à la fois. Mais honnêtement, je n'ai jamais vu cela se fait en C++ et je n'ai aucune idée de comment mettre en œuvre une telle chose.

Donc c'est soit RAII ou des exceptions. N'est-ce pas? Je suis penchée vers pas-de jeter des destructeurs; principalement parce qu'il garde les choses simples(r). Mais j'espère vraiment que il y a une meilleure solution, parce que, comme je l'ai dit, plus nous utilisons RAII, plus nous nous trouvons à l'aide de dtors que ne le font les non-triviales.

Annexe

Je suis ajoutant des liens intéressants sur le sujet des articles et des discussions que j'ai trouvé:

18voto

Loki Astari Points 116129

Vous ne DEVEZ PAS jeter une exception d'un destructeur.
Si une exception est déjà se propageant ensuite, l'application va se terminer.

Par résilier je veux dire arrêter immédiatement. Le déroulement de pile s'arrête. Pas plus destructeurs sont appelés. Toutes les mauvaises choses. Voir la discussion ici.

http://stackoverflow.com/questions/130117/throwing-exceptions-out-of-a-destructor#130123

Je ne suis pas (comme en désaccord avec) votre logique que cela provoque le destructeur pour obtenir plus compliqué.
Avec la bonne utilisation des pointeurs intelligents cela rend le destructeur de plus simple que maintenant tout devient automatique. Chaque classe marées son propre petit morceau du puzzle. Pas une chirurgie du cerveau ou la science de fusée ici. Une autre Grande victoire pour le RAII.

Quant à la possibilité de std::uncaught_exception() je vous en Herbe Sutters article sur pourquoi il ne fonctionne pas

9voto

Aaron Points 2456

À partir de la question d'origine:

Maintenant, de libération de la mémoire (finalisation, cependant, vous voulez l'appeler) peut échouer, auquel cas, des exceptions sont vraiment la seule façon de laisser quelqu'un à l'étage au courant de notre problème de libération de la mémoire

Échec de nettoyage d'une ressource, indique:

  1. Programmeur d'erreur, dans ce cas, vous devez vous connecter à l'échec, suivie par la notification de l'utilisateur ou de la cessation de l'application, selon le scénario de l'application. Par exemple, en libérant une allocation qui a déjà été libérée.

  2. L'allocateur de bug ou défaut de conception. Consultez la documentation. Les Chances sont que l'erreur est probablement là pour aider à diagnostiquer les erreurs du développeur. Voir le point 1 ci-dessus.

  3. Autrement irrécupérables indésirables condition qui peut être poursuivi.

Par exemple, le C++ gratuit magasin dispose d'un refus de l'échec de l'opérateur delete. D'autres Api (tels que Win32) fournir les codes d'erreur, mais échoue seulement en raison de programmeur d'erreur ou panne de matériel, avec des erreurs indiquant que les conditions comme la corruption de segment, ou double gratuit, etc.

Comme pour irrécupérable conditions défavorables, prendre la connexion DB. Si la fermeture de la connexion a échoué car la connexion a été interrompue -- cool, vous avez fait. Ne les jetez pas! La perte de la connexion (devrait) suite à une connexion fermée, donc il n'y a pas besoin de faire autre chose. Si quoi que ce soit, journal d'un message de trace pour aider à diagnostiquer les problèmes de l'utilisation. Exemple:

class DBCon{
public:
  DBCon() { 
    handle = fooOpenDBConnection();
  }
  ~DBCon() {
    int err = fooCloseDBConnection();
    if(err){
      if(err == E_fooConnectionDropped){
        // do nothing.  must have timed out
      } else if(fooIsCriticalError(err)){
        // critical errors aren't recoverable.  log, save 
        //  restart information, and die
        std::clog << "critical DB error: " << err << "\n";
        save_recovery_information();
        std::terminate();
      } else {
        // log, in case we need to gather this info in the future,
        //  but continue normally.
        std::clog << "non-critical DB error: " << err << "\n";
      }
    }
    // done!
  }
};

Aucune de ces conditions n'justifier tente une seconde nature de se détendre. Le programme peut continuer normalement (y compris exception détendez-vous, si le déroulement est en cours), ou il meurt ici et maintenant.

Modifier-Ajouter

Si vous vraiment voulez être en mesure de garder une sorte de lien pour ceux DB connexions qui ne peut pas fermer, peut-être ils n'ont pas réussi à fermer en raison de intermittentes conditions, et vous voulez réessayer plus tard-alors vous pouvez toujours reporter de nettoyage:

vector<DBHandle> to_be_closed_later;  // startup reserves space

DBCon::~DBCon(){
  int err = fooCloseDBConnection();
  if(err){
    ..
    else if( fooIsRetryableError(err) ){
      try{
        to_be_closed.push_back(handle);
      } catch (const bad_alloc&){
        std::clog << "could not close connection, err " << err << "\n"
      }
    }
  }
}

Très pas assez, mais il peut faire le travail pour vous.

5voto

paercebal Points 38526

Cela me rappelle une question d'un collègue quand je lui ai expliqué l'exception/RAII concepts: "Hé, exception puis-je jeter si l'ordinateur est éteint?"

De toute façon, je suis d'accord avec Martin York réponse http://stackoverflow.com/questions/159240/raii-vs-exceptions#159259

Quel est le problème avec les Exceptions et les Destructeurs?

Beaucoup de fonctionnalités C++ dépendent de non-lancement des destructeurs.

En fait, le concept de RAII et sa coopération avec le code de ramification (retours, prises, etc.) est basée sur le fait de désallocation de ne pas échouer. De la même façon, certaines fonctions ne sont pas supposés à l'échec (comme std::swap) quand vous voulez offrir à haute exception des garanties à vos objets.

Non pas que cela ne signifie pas que vous ne pouvez pas lancer des exceptions à travers des destructeurs. Juste que la langue n'essayez même pas à l'appui de ce comportement.

Qu'arriverait-il si elle était autorisée?

Juste pour le plaisir, j'ai essayé de l'imaginer...

Dans le cas où votre destructeur ne parvient pas à libérer vos ressources, qu'allez-vous faire? Votre objet est probablement la moitié détruite, que feriez-vous d'un "dehors" catch à cette info? Essayer de nouveau? (si oui, alors pourquoi ne pas essayer de nouveau de l'intérieur le destructeur?...)

C'est, si vous pouvez accéder à votre moitié détruits, des objets de toute façon: Que faire si votre objet est sur la pile (qui est la méthode de base RAII œuvres)? Comment pouvez-vous accéder à un objet en dehors de son champ d'application?

L'envoi de la ressource à l'intérieur de l'exception?

Votre seul espoir serait d'envoyer la "poignée" de la ressource à l'intérieur de l'exception et en espérant code dans la prise, eh bien... essayez de nouveau de désallouer (voir ci-dessus)?

Maintenant, imaginez quelque chose de drôle:

 void doSomething()
 {
    try
    {
       MyResource A, B, C, D, E ;

       // do something with A, B, C, D and E

       // Now we quit the scope...
       // destruction of E, then D, then C, then B and then A
    }
    catch(const MyResourceException & e)
    {
       // Do something with the exception...
    }
 }

Maintenant, imaginons que pour une raison quelconque, le destructeur de D ne parvient pas à libérer la ressource. Vous codés pour envoyer une exception, qui seront pris par les attraper. Tout va bien: Vous pouvez gérer la défaillance de la manière que vous voulez (comment vous allez de façon constructive échappe encore moi, mais ensuite, il n'est pas le problème).

Mais...

L'envoi de MULTIPLES ressources à l'intérieur de l'MULTIPLES exceptions?

Maintenant, si ~D peut échouer, alors ~C peut, aussi. ainsi que ~B et ~A.

Avec cet exemple simple, vous avez 4 destructeurs qui a échoué au "même moment" (abandon de la portée). Ce n'est pas un piège avec une exception, mais un piège avec un tableau des exceptions (espérons que le code généré pour ce n'est pas... euh... de les jeter).

    catch(const std::vector<MyResourceException> & e)
    {
       // Do something with the vector of exceptions...
       // Let's hope if was not caused by an out-of-memory problem
    }

Let's get retarted (j'aime cette musique...): Chaque exception est un autre (parce que la cause est différente: n'oubliez pas qu'en C++, les exceptions ne doivent pas dériver de std::exception). Maintenant, vous avez besoin pour traiter simultanément quatre exceptions. Comment pourriez-vous écrire les clauses catch de la manipulation de la quatre exceptions par leurs types, et par l'ordre qu'ils ont été jetés?

Et si vous avez plusieurs exceptions de même type, jetés par de multiples échec de la libération de la mémoire? Et si lors de l'allocation de la mémoire de l'exception des tableaux de tableaux, votre programme va de la mémoire et, euh... de jeter un souvenir d'exception?

Êtes-vous sûr que vous voulez passer du temps sur ce genre de problème au lieu de le dépenser comprendre pourquoi la libération de la mémoire a échoué ou la façon de réagir d'une autre manière?

Apprently, le C++ concepteurs ne voit pas une solution viable, juste et réduire leurs pertes.

2voto

DrPizza Points 9355

Une chose que je voudrais poser est, ignorant la question de la résiliation et ainsi de suite, que pensez-vous d'une réponse appropriée est que si votre programme ne peut pas fermer sa connexion DB, soit en raison de la normale à la destruction ou exceptionnel la destruction.

Vous semblez règle "simple enregistrement" et sont peu enclins à mettre fin, de sorte que pensez-vous est la meilleure chose à faire?

Je pense que si nous avons eu une réponse à cette question, alors nous aurions une meilleure idée de la façon de procéder.

Aucune stratégie semble particulièrement évident pour moi, en dehors de toute autre chose, je ne sais pas vraiment ce que cela signifie pour la fermeture d'une connexion de base de données pour lancer. Quel est l'état de la connexion si close() throws? Est-il fermé, ouvert, ou d'une durée indéterminée? Et si c'est de durée indéterminée, est-il un moyen pour que le programme puisse revenir à un état connu?

Un destructeur à défaut signifie qu'il n'y a aucun moyen d'annuler la création d'un objet; la seule façon de revenir le programme à un autre (safe) de l'état est d'abattre l'ensemble du processus et de recommencer.

1voto

Tanktalus Points 7940

Quelles sont les raisons de la destruction peut échouer? Pourquoi ne pas regarder pour le traitement de ces avant de la destruction?

Par exemple, la fermeture d'une connexion de base de données peut être parce que:

  • Transaction en cours. (Check std::uncaught_exception() - si la valeur est true, la fonction de restauration, d'autre commit - ces sont le plus souvent les actions de votre choix, sauf si vous avez une politique qui dit le contraire, avant la fermeture de la connexion.)
  • La connexion est abandonnée. (Détecter et de les ignorer. Le serveur annulera automatiquement.)
  • D'autres DB erreur. (Journal, afin que nous puissions enquêter et éventuellement de gérer de manière appropriée dans l'avenir. Ce qui peut être à détecter et à ignorer. En attendant, essayez de restauration et débranchez de nouveau et d'ignorer toutes les erreurs.)

Si je comprends RAII correctement (ce qui je ne pourrais pas), l'ensemble de son champ d'application. Il n'est donc pas comme vous VOULEZ opérations d'une durée supérieure à l'objet, de toute façon. Il me semble raisonnable, alors, que vous voulez pour assurer la fermeture du mieux que vous pouvez. RAII ne fait pas de cet unique - même sans objets (par exemple en C), vous n'auriez toujours essayer d'attraper toutes les conditions d'erreur et de traiter avec eux du mieux que vous pouvez (ce qui est parfois de les ignorer). Tous les RAII n'est de vous forcer à mettre ce code dans un seul endroit, n'importe comment beaucoup de fonctions sont à l'aide de ce type de ressource.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X