Sobes.tech
Middle+

Mikor nem hívódnak a destruktorok kivétel (exception) hívásakor?

sobes.tech MI

Válasz az MI-től

A helyi automatikus életidejű objektumok destruktorai nem hívódnak meg, ha kivétel dobódik a konstruktorban, és az objektum nem lett teljesen létrehozva.

Szintén nem hívódnak meg destruktorok:

  • Globális vagy statikus életidejű objektumokra.
  • Nyers memóriaobjektumokra, amelyeket nem kezelnek szabványos mechanizmusok (pl. malloc-on keresztül lefoglalt és nem szabadult fel).
  • Olyan objektumokra, amelyekre utalnak szivárgó okosmutatók.
  • Olyan objektumokra, amelyek destruktorai noexcept(false)-ként vannak megjelölve, és maguk is kivételt dobnak a veremkibontás során (stack unwinding). Ebben az esetben a program általában hibával (std::terminate) ér véget.

Példa a destruktor nem hívására a konstruktorban:

#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; // Ez a destruktor meghívódik, ha kivétel dobódik a MyClass konstruktorában
    // Resource res2; // Ha itt dobna kivételt, res2 nem lenne létrehozva

    MyClass() {
        // res1 már létrejött
        std::cout << "MyClass constructor" << std::endl;
        throw std::runtime_error("Error in MyClass constructor");
        // res2 = Resource(); // Ez az objektum nem jönne létre, ha az előző kivétel dobódik
    }
    ~MyClass() {
        std::cout << "MyClass destructor" << std::endl;
    }
};

int main() {
    try {
        MyClass obj; // Itt kivétel keletkezik
    } catch (const std::runtime_error& e) {
        std::cerr << "Caught exception: " << e.what() << std::endl;
    }
    return 0;
}

Ebben a példában a MyClass destruktor nem hívódik meg, mivel a MyClass konstruktor nem sikerült teljesen. Azonban a res1 destruktor (ami a MyClass-hez tartozik) meghívódik, mivel a res1 sikeresen létrejött, mielőtt kivétel keletkezett.

Példa a destruktor nem hívására ismételt kivétel esetén:

#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"); // Kivétel dobása a destruktorban
    }
};

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;
    }
    // A `std::terminate` lesz hívva, mielőtt ez a blokk végrehajtódna
    return 0;
}

Ebben az esetben, amikor kivétel keletkezik a foo-ban, a veremkibontás megkezdődik. Meghívódik a BadDestructor destruktora. Mivel a BadDestructor destruktora kivételt dob (noexcept(false) lehetővé teszi ezt), ismételt kivétel keletkezik a jelenlegi kivétel feldolgozása közben. A C++ szabvány szerint ilyen helyzetben a std::terminate lesz hívva, és a program leáll. A többi objektum destruktora a veremben (ha voltak, mielőtt a foo-t meghívta) nem biztos, hogy meghívódnak.