Preguntas de excepciones de C ++ sobre la repetición de la excepción original

117

¿El siguiente append () en la captura provocará que se vuelva a lanzar la excepción para ver el efecto de llamar a append ()?

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

De manera similar, si lo reescribo de esta manera, ¿se producirá un corte de bits si myErr deriva la excepción real?

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
fuente

Respuestas:

150

En ambos casos, dado que captura por referencia, está alterando efectivamente el estado del objeto de excepción original (que puede pensar que reside en una ubicación de memoria mágica que seguirá siendo válida durante el desenrollado posterior , 0x98e7058en el ejemplo siguiente). Sin embargo,

  1. En el primer caso, ya que volver a lanzar con throw;(que, a diferencia de throw err;, preserva el objeto de excepción original, con sus modificaciones, en dicha "lugar mágico" a 0x98e7058) va a reflejar la llamada a modo de adición ()
  2. En el segundo caso, dado que arroja algo explícitamente, se creará una copia de y errluego se lanzará de nuevo (en una "ubicación mágica" diferente 0x98e70b0, porque por lo que el compilador sabe, errpodría haber un objeto en la pila a punto de desenrollarse, como efue en 0xbfbce430, no en la "ubicación mágica" en 0x98e7058), por lo que perderá datos específicos de la clase derivada durante la construcción de la copia de una instancia de clase base.

Programa simple para ilustrar lo que está sucediendo:

#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

Ver también:

vladr
fuente
24

Esta pregunta es bastante antigua y tiene una respuesta adecuada al momento en que se hizo. Sin embargo, solo quiero agregar una nota sobre cómo hacer el manejo adecuado de excepciones desde C ++ 11 y creo que esto corresponde muy bien a lo que estaba tratando de lograr con su función de adición:

Utilice std::nested_exceptionystd::throw_with_nested

Se describe en StackOverflow aquí y aquí , cómo puede obtener un seguimiento de sus excepciones dentro de su código sin necesidad de un depurador o un registro engorroso, simplemente escribiendo un controlador de excepciones adecuado que volverá a generar excepciones anidadas.

Dado que puede hacer esto con cualquier clase de excepción derivada, ¡puede agregar mucha información a dicho seguimiento! También puede echar un vistazo a mi MWE en GitHub , donde un backtrace se vería así:

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

Sí, volver a lanzar vuelve a generar el objeto de excepción original, que ha modificado mediante una referencia. También puede capturar una referencia de clase base, modificarla y aún así poder volver a generar el tipo de excepción derivado original por throw;.

Tronic
fuente
1

para la primera pregunta, sí.

pero para el segundo, consulte la respuesta de Vlad. deberá diseñar cuidadosamente su objeto de excepción para manejar la copia ctor. por convención, la clase base no reconoce a su hijo, por lo que probablemente perderá los datos adicionales que lleva la clase derivada.

YeenFei
fuente