Pytania dotyczące wyjątków C ++ dotyczące ponownego zgłaszania oryginalnego wyjątku

117

Czy następująca metoda append () w catch spowoduje, że ponownie zgłoszony wyjątek zobaczy efekt wywołania metody append ()?

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

Podobnie, jeśli przepiszę to w ten sposób, czy fragmentowanie bitów wystąpi, jeśli rzeczywisty wyjątek pochodzi z 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
źródło

Odpowiedzi:

150

W obu przypadkach, ponieważ łapiesz przez odniesienie, skutecznie zmieniasz stan oryginalnego obiektu wyjątku (który możesz pomyśleć jako rezydujący w magicznej lokalizacji pamięci, która pozostanie ważna podczas późniejszego rozwijania - 0x98e7058w przykładzie poniżej). Jednak,

  1. W pierwszym przypadku, ponieważ wyrzucasz ponownie za pomocą throw;(który w przeciwieństwie do throw err;zachowuje oryginalny obiekt wyjątku, z twoimi modyfikacjami, we wspomnianym „magicznym miejscu” w 0x98e7058) będzie odzwierciedlać wywołanie append ()
  2. W drugim przypadku, skoro rzucasz coś wyraźnie, A copy of errzostanie utworzony potem rzucony na nowo (w innym miejscu „magicznym” 0x98e70b0- bo wszystko kompilator wie, errmoże być obiekt na stosie o się rozwinąć, jak ebyło at 0xbfbce430, a nie w „magicznej lokalizacji” at 0x98e7058), więc podczas kopiowania instancji klasy bazowej utracisz dane specyficzne dla klasy pochodnej .

Prosty program ilustrujący, co się dzieje:

#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;
}

Wynik:

  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

Zobacz także:

vladr
źródło
24

To pytanie jest dość stare i ma odpowiedź odpowiednią do czasu, w którym zostało zadane. Chciałbym jednak tylko dodać uwagę, jak prawidłowo obsługiwać wyjątki od czasu C ++ 11 i uważam, że bardzo dobrze odpowiada to temu, co próbujesz osiągnąć za pomocą funkcji dołączania:

Użyj std::nested_exceptionistd::throw_with_nested

Opisano w StackOverflow tutaj i tutaj , w jaki sposób można uzyskać śledzenie wstecz dla wyjątków w kodzie bez potrzeby debugera lub kłopotliwego rejestrowania, po prostu pisząc odpowiednią procedurę obsługi wyjątków, która ponownie wyrzuci zagnieżdżone wyjątki.

Ponieważ możesz to zrobić z każdą pochodną klasą wyjątków, możesz dodać wiele informacji do takiego śledzenia wstecznego! Możesz również rzucić okiem na moje MWE na GitHub , gdzie ślad cofania wyglądałby mniej więcej tak:

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
źródło
8

Tak, ponowne zgłoszenie powoduje ponowne zgłoszenie oryginalnego obiektu wyjątku, który został zmodyfikowany przez odniesienie. Możesz również przechwycić odwołanie do klasy bazowej, zmodyfikować je i nadal być w stanie ponownie zgłosić oryginalny pochodny typ wyjątku przez throw;.

Tronic
źródło
1

na pierwsze pytanie, tak.

ale po drugie, zapoznaj się z odpowiedzią Vlada. będziesz musiał ostrożnie zaprojektować swój wyjątek do obsługi kopiowania ctor. zgodnie z konwencją klasa bazowa nie rozpoznaje swojego dziecka, więc najprawdopodobniej stracisz dodatkowe dane przenoszone przez klasę pochodną.

YeenFei
źródło