Sobes.tech
Middle+

Povězte o výzvách při házení výjimek v konstruktorách a destruktorách.

sobes.tech AI

Odpověď od AI

Volání výjimky v konstruktoru může vést ke ztrátě zdrojů, pokud část objektů nebo zdrojů uvnitř konstruktoru byla úspěšně alokována před vyhozením výjimky, a odpovídající destruktor nebude zavolán. Aby se zabránilo únikům, doporučuje se používat RAII (Resource Acquisition Is Initialization), například pomocí chytrých ukazatelů nebo obalových tříd, které zaručují uvolnění zdrojů při opuštění rozsahu.

Volání výjimky v destruktoru vede k nedefinovanému chování. Pokud je destruktor volán v důsledku jiných výjimek (stack unwinding), a sám vyhodí neopracovanou výjimku, program skončí voláním std::terminate. Podle standardu C++ by destruktory měly být noexcept.

#include <iostream>
#include <memory> // Pro std::unique_ptr

struct Resource {
    Resource() { std::cout << "Získání zdroje\n"; }
    ~Resource() { std::cout << "Uvolnění zdroje\n"; }
};

struct BadConstructor {
    Resource res; // Zdroj bude alokován před vyhozením výjimky
    BadConstructor() {
        std::cout << "Konstruktor BadConstructor\n";
        throw std::runtime_error("Chyba v konstruktoru"); // Vyhození výjimky
        // Destruktor Resource nebude volán, pokud je výjimka vyhozena před dokončením konstruktoru
    }
    ~BadConstructor() noexcept { // Destruktory by měly být noexcept
        std::cout << "Destruktor BadConstructor\n";
    }
};

struct GoodConstructor {
    std::unique_ptr<Resource> res; // Použití chytrého ukazatele pro RAII
    GoodConstructor() : res(std::make_unique<Resource>()) {
        std::cout << "Konstruktor GoodConstructor\n";
        // Pokud je vyhozena výjimka, unique_ptr zajistí volání destruktoru Resource při opuštění rozsahu
        // throw std::runtime_error("Chyba v konstruktoru");
    }
    ~GoodConstructor() noexcept {
        std::cout << "Destruktor GoodConstructor\n";
    }
};

struct BadDestructor {
    BadDestructor() { std::cout << "Konstruktor BadDestructor\n"; }
    ~BadDestructor() {
        std::cout << "Destruktor BadDestructor\n";
        // Není vhodné vyhazovat výjimku z destruktoru, zejména při unwinding
        // throw std::runtime_error("Chyba v destruktoru");
    }
};

int main() {
    // Příklad s konstruktoru (vede ke ztrátě zdroje bez RAII)
    try {
        // BadConstructor bc; // Odkomentovat pro demonstraci ztráty
    } catch (const std::exception& e) {
        std::cout << "Zachycena výjimka: " << e.what() << "\n";
    }

    // Příklad s dobrým konstruktoru (používá RAII)
    try {
        // GoodConstructor gc; // Odkomentovat pro demonstraci, žádná ztráta
    } catch (const std::exception& e) {
        std::cout << "Zachycena výjimka: " << e.what() << "\n";
    }

    // Příklad s destruktoru (může vést k std::terminate)
    try {
        // BadDestructor bd; // Odkomentovat, pokud chcete vidět potenciální terminate
        // throw std::runtime_error("Další výjimka"); // způsobí unwind
    } catch (const std::exception& e) {
        std::cout << "Zachycena výjimka: " << e.what() << "\n";
    }

    return 0;
}

Tabulka:

Místo volání výjimky Potenciální problémy Doporučené řešení
Konstruktor Únik zdrojů Používat RAII (chytré ukazatele, obalové třídy)
Destruktor Nedefinované chování, std::terminate při unwind Destruktory by měly být noexcept, vyhýbat se vyhazování výjimek