Questions sur les exceptions C ++ sur la relance de l'exception d'origine

117

L'append () suivant dans le catch provoquera-t-il que l'exception rethrown voit l'effet de l'appel de append ()?

try {
  mayThrowMyErr();
} catch (myErr &err) {
  err.append("Add to my message here");
  throw; // Does the rethrow exception reflect the call to append()?
}

De même, si je le réécris de cette façon, un découpage de bits se produira-t-il si l'exception réelle est dérivée par myErr?

try {
  mayThrowObjectDerivedFromMyErr();
} catch (myErr &err) {
  err.append("Add to my message's base class here");
  throw err; // Do I lose the derived class exception and only get myErr?
}
WilliamKF
la source

Réponses:

150

Dans les deux cas, puisque vous attrapez par référence, vous modifiez effectivement l'état de l'objet d'exception d'origine (que vous pouvez considérer comme résidant dans un emplacement de mémoire magique qui restera valide lors du déroulement suivant - 0x98e7058dans l'exemple ci-dessous). cependant,

  1. Dans le premier cas, puisque vous rethrow avec throw;(qui, contrairement throw err;, préserve l'objet d'exception d' origine, avec vos modifications, dans ledit « lieu magique » à 0x98e7058) va tenir compte de l'appel à append ()
  2. Dans le second cas, puisque vous lancez quelque chose explicitement, une copie de errsera créée puis lancée à nouveau (à un autre "emplacement magique" 0x98e70b0- car pour tout ce que le compilateur sait, il errpourrait y avoir un objet sur la pile sur le point d'être déroulé, comme ce efut at 0xbfbce430, pas dans "l'emplacement magique" at 0x98e7058), vous perdrez donc les données spécifiques à la classe dérivée lors de la construction de copie d'une instance de classe de base.

Programme simple pour illustrer ce qui se passe:

#include <stdio.h>

struct MyErr {
  MyErr() {
    printf("  Base default constructor, this=%p\n", this);
  }
  MyErr(const MyErr& other) {
    printf("  Base copy-constructor, this=%p from that=%p\n", this, &other);
  }
  virtual ~MyErr() {
    printf("  Base destructor, this=%p\n", this);
  }
};

struct MyErrDerived : public MyErr {
  MyErrDerived() {
    printf("  Derived default constructor, this=%p\n", this);
  }
  MyErrDerived(const MyErrDerived& other) {
    printf("  Derived copy-constructor, this=%p from that=%p\n", this, &other);
  }
  virtual ~MyErrDerived() {
    printf("  Derived destructor, this=%p\n", this);
  }
};

int main() {
  try {
    try {
      MyErrDerived e;
      throw e;
    } catch (MyErr& err) {
      printf("A Inner catch, &err=%p\n", &err);
      throw;
    }
  } catch (MyErr& err) {
    printf("A Outer catch, &err=%p\n", &err);
  }
  printf("---\n");
  try {
    try {
      MyErrDerived e;
      throw e;
    } catch (MyErr& err) {
      printf("B Inner catch, &err=%p\n", &err);
      throw err;
    }
  } catch (MyErr& err) {
    printf("B Outer catch, &err=%p\n", &err);
  }
  return 0;
}

Résultat:

  Base default constructor, this=0xbfbce430
  Derived default constructor, this=0xbfbce430
  Base default constructor, this=0x98e7058
  Derived copy-constructor, this=0x98e7058 from that=0xbfbce430
  Derived destructor, this=0xbfbce430
  Base destructor, this=0xbfbce430
A Inner catch, &err=0x98e7058
A Outer catch, &err=0x98e7058
  Derived destructor, this=0x98e7058
  Base destructor, this=0x98e7058
---
  Base default constructor, this=0xbfbce430
  Derived default constructor, this=0xbfbce430
  Base default constructor, this=0x98e7058
  Derived copy-constructor, this=0x98e7058 from that=0xbfbce430
  Derived destructor, this=0xbfbce430
  Base destructor, this=0xbfbce430
B Inner catch, &err=0x98e7058
  Base copy-constructor, this=0x98e70b0 from that=0x98e7058
  Derived destructor, this=0x98e7058
  Base destructor, this=0x98e7058
B Outer catch, &err=0x98e70b0
  Base destructor, this=0x98e70b0

Regarde aussi:

vladr
la source
24

Cette question est assez ancienne et a une réponse appropriée à l'heure à laquelle elle a été posée. Cependant, je veux juste ajouter une note sur la façon de gérer correctement les exceptions depuis C ++ 11 et je pense que cela correspond très bien à ce que vous essayiez de réaliser avec votre fonction d'ajout:

Utiliser std::nested_exceptionetstd::throw_with_nested

Il est décrit sur StackOverflow ici et ici , comment vous pouvez obtenir une trace de vos exceptions dans votre code sans avoir besoin d'un débogueur ou d'une journalisation fastidieuse, en écrivant simplement un gestionnaire d'exceptions approprié qui renverra les exceptions imbriquées.

Comme vous pouvez le faire avec n'importe quelle classe d'exception dérivée, vous pouvez ajouter beaucoup d'informations à une telle trace! Vous pouvez également jeter un œil à mon MWE sur GitHub , où une trace de retour ressemblerait à ceci:

Library API: Exception caught in function 'api_function'
Backtrace:
~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed
~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt"
GPMueller
la source
8

Oui, la relance relance l'objet d'exception d'origine, que vous avez modifié par une référence. Vous pouvez également intercepter une référence de classe de base, la modifier et toujours pouvoir renvoyer le type d'exception dérivé d'origine par throw;.

Tronic
la source
1

pour la première question, oui.

mais pour la seconde, reportez-vous à la réponse de Vlad. vous devrez concevoir soigneusement votre objet d'exception pour gérer le cteur de copie. par convention, la classe de base ne reconnaît pas son enfant donc vous perdrez très probablement les données supplémentaires portées par la classe dérivée.

YeenFei
la source