Sobes.tech
Middle+

Kedy sa pri volaní výnimky (exception) nevolajú destruktory?

sobes.tech AI

Odpoveď od AI

Deštruktory lokálnych objektov s automatickým časom života sa nevolajú, ak je v konštruktore vyhodená výnimka a objekt nebol úplne vytvorený.

Tiež sa nevolajú deštruktory pre:

  • Objektov s globálnym alebo statickým časom života.
  • Objektov v surovej pamäti, ktoré nie sú spravované štandardnými mechanizmami (napríklad alokované cez malloc a neboli uvoľnené).
  • Objektov, na ktoré ukazujú unikajúce chytré ukazovatele.
  • Objektov, ktorých deštruktory sú označené ako noexcept(false) a sami vyhadzujú výnimku počas rozbalenia zásobníka (stack unwinding). V takom prípade sa program zvyčajne ukončí s chybou (std::terminate).

Príklad nevolania deštruktora v konštruktore:

#include <iostream>
#include <stdexcept>

class Resource {
public:
    Resource() {
        std::cout << "Resource acquired" << std::endl;
    }
    ~Resource() {
        std::cout << "Resource released" << std::endl;
    }
};

class MyClass {
public:
    Resource res1; // Tento deštruktor bude volaný pri výnimke v konštruktore MyClass
    // Resource res2; // Ak tu hodíme výnimku, res2 nebude vytvorený

    MyClass() {
        // res1 je už vytvorený
        std::cout << "MyClass constructor" << std::endl;
        throw std::runtime_error("Error in MyClass constructor");
        // Resource res2 = Resource(); // Tento objekt nebude vytvorený, ak je skôr hodená výnimka
    }
    ~MyClass() {
        std::cout << "MyClass destructor" << std::endl;
    }
};

int main() {
    try {
        MyClass obj; // Tu sa hodí výnimka
    } catch (const std::runtime_error& e) {
        std::cerr << "Caught exception: " << e.what() << std::endl;
    }
    return 0;
}

V tomto príklade sa deštruktor MyClass nevolá, pretože konštruktor MyClass nebol úspešne dokončený. Avšak deštruktor res1 (ktorý patrí MyClass) bude volaný, pretože res1 bol úspešne vytvorený pred tým, ako bola vyhodená výnimka.

Príklad nevolania deštruktora pri opakovanom vyhodení výnimky:

#include <iostream>
#include <stdexcept>

class BadDestructor {
public:
    BadDestructor() {
        std::cout << "BadDestructor constructor" << std::endl;
    }
    ~BadDestructor() noexcept(false) {
        std::cout << "BadDestructor destructor" << std::endl;
        throw std::runtime_error("Error in destructor"); // Vyhadzuje výnimku v deštruktore
    }
};

void foo() {
    BadDestructor obj;
    throw std::runtime_error("Exception in foo");
}

int main() {
    try {
        foo();
    } catch (const std::runtime_error& e) {
        std::cerr << "Caught exception: " << e.what() << std::endl;
    }
    // `std::terminate` bude volané skôr, než sa tento blok vykoná
    return 0;
}

V tomto prípade, keď je v foo vyhodená výnimka, začína sa rozbaľovanie zásobníka. Volá sa deštruktor BadDestructor. Pretože deštruktor BadDestructor vyhadzuje výnimku (noexcept(false) to umožňuje), dochádza k opätovnému vyhodenie výnimky počas spracovania aktuálnej výnimky. Podľa štandardu C++, v takejto situácii sa volá std::terminate, a program sa ukončí. Deštruktory iných objektov na zásobníku (ak by boli pred foo) nemusia byť volané.