web-dev-qa-db-fra.com

Questions d'exceptions C ++ sur le retour de l'exception d'origine

Est-ce que l'append () suivant dans le catch provoquera l'exception de rethrown pour voir l'effet d'append () appelé?

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, le découpage des 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?
}
103
WilliamKF

Dans les deux cas, puisque vous capturez 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 n emplacement de mémoire magique qui restera valide pendant le déroulement suivant - - 0x98e7058 dans l'exemple ci-dessous). cependant,

  1. Dans le premier cas, puisque vous relancez avec throw; (qui, contrairement à throw err;, conserve l'objet d'exception d'origine, avec vos modifications, dans ledit "emplacement magique" à 0x98e7058) va refléter l'appel à ajouter ()
  2. Dans le second cas, puisque vous lancez quelque chose de manière explicite, une copie de err sera créée puis relancée (à un autre "magique" emplacement" 0x98e70b0 - car pour tout le compilateur sait que err pourrait être un objet sur la pile sur le point d'être déroulé, comme e était à 0xbfbce430, pas dans "l'emplacement magique" à 0x98e7058), donc vous perdrez des données spécifiques à la classe dérivée pendant la construction de la 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:

139
vladr

Cette question est assez ancienne et a une réponse appropriée au moment où 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 étiez essayer d'atteindre avec votre fonction d'ajout:

Utilisation std::nested_exception et std::throw_with_nested

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

Puisque 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 oeil à mon MWE sur GitHub , où une trace ressemblerait à quelque chose comme 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"
14
GPMueller

Oui, la relance renvoie à nouveau 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 relancer le type d'exception dérivée d'origine par throw;.

8
Tronic

pour la première question, oui.

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

1
YeenFei