С++ Исключения вопросы о повторном исходном исключении

Будет ли следующее приложение() в 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 base class here");
  throw err; // Do I lose the derived class exception and only get myErr?
}

Ответ 1

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

  • В первом случае, поскольку вы ревертируете с помощью throw; (который, в отличие от throw err;, сохраняет исходный объект исключения с вашими изменениями в указанном "магическом месте" в 0x98e7058) будет отражать вызов для добавления()
  • Во втором случае, поскольку вы явно бросаете что-то, копировать из err будет создаваться, а затем забрасываться заново (в другом "волшебном месте" 0x98e70b0 - потому что для всех компилятор знает, что err может быть объектом в стеке, который должен быть развязан, например, 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

Также смотрите:

Ответ 2

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

Ответ 3

для первого вопроса, да.

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

Ответ 4

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

Используйте std::nested_exception и std::throw_with_nested

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

Поскольку вы можете сделать это с помощью любого производного класса исключения, вы можете добавить много информации в такую ​​обратную линию! Вы также можете взглянуть на мой 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"