Вопросы об исключениях C ++ при повторном вызове исходного исключения

117

Будет ли следующий метод append () в catch вызывать повторное создание исключения, чтобы увидеть эффект от вызова append ()?

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

Точно так же, если я перепишу его таким образом, произойдет ли нарезка битов, если фактическое исключение будет получено с помощью 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
источник

Ответы:

150

В обоих случаях, поскольку вы перехватываете по ссылке, вы эффективно изменяете состояние исходного объекта исключения (который вы можете рассматривать как находящийся в волшебной области памяти, которая останется действительной во время последующей раскрутки - 0x98e7058в примере ниже). Тем не мение,

  1. В первом случае, поскольку вы повторно создаете с помощью throw;(который, в отличие от throw err;, сохраняет исходный объект исключения с вашими изменениями в указанном «волшебном месте» в 0x98e7058), будет отражен вызов append ()
  2. Во втором случае, так как вы бросаете что - то явно, копия из errбудет создано затем выброшен заново (в другом «волшебном месте» 0x98e70b0- потому что для всех компилятор знает , errможет быть объект в стеке около быть unwinded, как eбыло в 0xbfbce430, а не в «волшебном месте» в 0x98e7058), поэтому вы потеряете данные, зависящие от производного класса, во время создания копии экземпляра базового класса.

Простая программа для иллюстрации происходящего:

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

Результат:

  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

Также см:

vladr
источник
24

Это довольно старый вопрос, и на него есть ответ, соответствующий тому времени, когда он был задан. Однако я просто хочу добавить примечание о том, как правильно обрабатывать исключения, начиная с C ++ 11, и я считаю, что это очень хорошо соответствует тому, что вы пытались достичь с помощью своей функции добавления:

Использование std::nested_exceptionиstd::throw_with_nested

Здесь и здесь описано в StackOverflow , как вы можете получить обратную трассировку для ваших исключений внутри вашего кода без необходимости в отладчике или громоздком журналировании, просто написав правильный обработчик исключений, который повторно генерирует вложенные исключения.

Так как вы можете сделать это с любым производным классом исключений, вы можете добавить много информации в такую ​​трассировку! Вы также можете взглянуть на мою MWE на GitHub , где трассировка будет выглядеть примерно так:

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
источник
8

Да, при повторном генерировании повторно генерируется исходный объект исключения, который вы изменили с помощью ссылки. Вы также можете перехватить ссылку на базовый класс, изменить ее и по-прежнему иметь возможность повторно генерировать исходный производный тип исключения с помощью throw;.

Tronic
источник
1

на первый вопрос, да.

а во-вторых, обратитесь к ответу Влада. вам нужно будет тщательно спроектировать свой объект исключения для обработки ctor копирования. по соглашению базовый класс не распознает своего дочернего элемента, поэтому вы, скорее всего, потеряете дополнительные данные, переносимые производным классом.

YeenFei
источник