Perguntas de exceções C ++ sobre o relançamento da exceção original

117

O append () a seguir no catch fará com que a exceção relançada veja o efeito de append () sendo chamado?

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

Da mesma forma, se eu reescrever dessa forma, ocorrerá o fatiamento de bits se a exceção real for derivada de 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
fonte

Respostas:

150

Em ambos os casos, uma vez que você pega por referência, você está efetivamente alterando o estado do objeto de exceção original (que você pode pensar como residindo em um local de memória mágico que permanecerá válido durante o desenrolar subsequente - 0x98e7058no exemplo abaixo). Contudo,

  1. No primeiro caso, desde que você relançar com throw;(que, ao contrário throw err;, preserva o objeto de exceção original, com suas modificações, no referido "local mágico" no 0x98e7058) irá refletir a chamada para append ()
  2. No segundo caso, uma vez que você joga algo explicitamente, uma cópia de errserá criada e então lançada de novo (em um "local mágico" diferente 0x98e70b0- porque, pelo que o compilador sabe, errpoderia ser um objeto na pilha prestes a ser desenrolado, como efoi em 0xbfbce430, não no "local mágico" em 0x98e7058), então você perderá dados específicos da classe derivada durante a construção de cópia de uma instância de classe base.

Programa simples para ilustrar o que está acontecendo:

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

Resultado:

  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

Veja também:

vladr
fonte
24

Esta pergunta é bastante antiga e tem uma resposta apropriada à época em que foi feita. No entanto, eu só quero adicionar uma observação sobre como fazer o tratamento adequado de exceções desde C ++ 11 e acredito que isso corresponde muito bem ao que você estava tentando alcançar com a função append:

Use std::nested_exceptionestd::throw_with_nested

É descrito em StackOverflow aqui e aqui , como você pode obter um backtrace em suas exceções dentro de seu código sem a necessidade de um depurador ou registro complicado, simplesmente escrevendo um manipulador de exceção adequado que irá relançar exceções aninhadas.

Visto que você pode fazer isso com qualquer classe de exceção derivada, você pode adicionar muitas informações a esse backtrace! Você também pode dar uma olhada no meu MWE no GitHub , onde um backtrace seria mais ou menos assim:

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
fonte
8

Sim, relançar relança o objeto de exceção original, que você modificou por uma referência. Você também pode capturar uma referência de classe base, modificá-la e ainda ser capaz de relançar o tipo de exceção derivado original por throw;.

Tronic
fonte
1

para a primeira pergunta, sim.

mas em segundo lugar, consulte a resposta de Vlad. você precisará projetar cuidadosamente seu objeto de exceção para lidar com o ctor de cópia. por convenção, a classe base não reconhece seu filho, então provavelmente você perderá os dados adicionais transportados pela classe derivada.

YeenFei
fonte