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

Приведет ли следующий метод append() в перехвате к тому, чтобы переброшенное исключение увидело эффект вызова 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?
}

5 ответов

Решение

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

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

Также см:

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

использование std::nested_exception а также std::throw_with_nested

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

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

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

Стандарт С++ 2003 был в то время активным стандартом, когда был задан этот вопрос. Имейте в виду, что язык C++ имеет стандарт.

https://www.open-std.org/JTC1/SC22/WG21/docs/papers/2005/n1905.pdf

с.351, с.15.4:

When the handler declares a non-constant object, any changes to that object will not affect the temporary object that was initialized by execution of the throw-expression. When the handler declares a reference to a non-constant object, any changes to the referenced object are changes to the temporary object initialized when the throw expression was executed and will have effect should that object be rethrown.

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

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

Единственный способ быть уверенным на 100% - заглянуть в Standard for Programming Language.

      #include <iostream>

using std::cout;

class A{};

void f1() {
    throw A();
}

void f2()
{
    try {
        f1();
    }
    catch(A& obj) {
        cout << "f1 obj: " << &obj << "\n";
        throw;
    }
}

void f3()
{
    try {
        f2();
    }
    catch(A& obj) {
        cout << "f3 obj: " << &obj << "\n";
        throw;
    }
}

int main()
{
    try {
        f3();
    }
    catch(A& obj)
    {
        cout << "main obj: " << &obj;
    }
    
    return 0;
}

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

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

Другие вопросы по тегам