Aufruf von Destruktoren bei Exception



  • Hallo,

    ich habe zwei etwas speziellere Fragen zum Aufruf von Destruktoren bzw. stack unwinding beim Werfen von Exceptions. Gegeben sei folgender Code:

    try
    {
        A a;
        B b(a); // a wird als Member von b gespeichert
    
        b.foo();
    }
    catch(...)
    {
        cerr << "Fehler";
    }
    

    1. Was passiert, wenn foo() eine Exception wirft? Wird dann der Destruktor erst für a als Member von b und dann ein weiteres Mal für a als lokale Variable im try-Block aufgerufen?

    2. Was passiert, wenn bereits der Konstruktur von b eine Exception wirft? Der Destruktor von b wird dann schon einmal nicht aufgerufen, soviel weiß ich bereits, aber was ist mit dem von a?

    Mich interessiert vor allem die genaue Reihenfolge von Destruktoraufrufen und Ausführung des catch-Blocks. Ich hoffe, dass man das anhand des Beispiels überhaupt sicher sagen kann, ansonsten müssten noch weitere Angaben gemacht werden.



  • 2. Was passiert, wenn bereits der Konstruktur von b eine Exception wirft? Der Destruktor von b wird dann schon einmal nicht aufgerufen, soviel weiß ich bereits, aber was ist mit dem von a?

    der von a wird aufgerufen, da a schon konstruiert wurde.

    1. Was passiert, wenn foo() eine Exception wirft? Wird dann der Destruktor erst für a als Member von b und dann ein weiteres Mal für a als lokale Variable im try-Block aufgerufen?

    ja, A::~A wird 2x ausgeführt, wenn B ein A beinhaltet (dann wurde es im CTor ja kopiert). Wenn A nur eine Referenz oder einen Pointer hält, wird der DTor logischerweise nur einmal aufgerufen.

    Reihenfolge, wenn foo wirft (editiert - da war ich wohl nicht recht bei der sache^^):

    foo()
    
    B::~B
    {
      A::~A
      {}
    }
    A::~A
    {}
    

    wenn es an dem bsp noch nicht klar wurde:
    (die zu erst erstellten Objekte werden zu letzt zerstört)
    gegeben sei:

    class A
    {
      A()  {std::cout << "A::A()" << std::endl;}
      ~A() {std::cout << "A::~A()" << std::endl;}
    };
    class B
    {
      B()  {std::cout << "B::B()" << std::endl;}
      ~B() {std::cout << "B::~B()" << std::endl;}
    };
    class C
    {
      C()  {std::cout << "C::C()" << std::endl;}
      ~C() {std::cout << "C::~C()" << std::endl;}
    };
    

    anwendung:

    void foo()
    {
      std::cout << "foo()" << std::endl;
    
      throw 0;
      /*unreachable code*/
    }
    
    int main()
    {
      try
      {
        A a;
        B b;
        C c;
    
        foo();
      }
      catch(...)
      {
        std::cerr << "cought" << std::endl;
      }
    }
    

    ausgabe wäre:

    A::A()
    B::B()
    C::C()
    foo()
    C::~C()
    B::~B()
    A::~A()
    cought
    

    bb



  • Danke, das macht die ganze Sache schon wesentlich klarer 🙂 Das heißt also, um bei meinem Beispiel zu bleiben, dass bei folgender Definition der Klasse B und einer Exception in foo() zuerst der Destruktor des Members a_, dann der Destruktor der lokalen Variable b im try-Block, dann jener der lokalen Variable a im try-Block und erst dann der catch-Block ausgeführt wird?

    class B
    {
    public:
        B(A a) : a_(a)
        {}
    
        void foo();
    
    private:
        A a_;
    };
    

    Oder würde A::~A hier sogar dreimal ausgeführt, da der Kopierkonstruktor zwei neue Objekte erzeugt?



  • Mach doch eine Ausgabe in den Destruktoren und probier es aus.


Anmelden zum Antworten