Sobes.tech
Middle+

Vertel over de uitdagingen van het gooien van uitzonderingen (throw exception) in constructeurs en destructeurs.

sobes.tech AI

Antwoord van AI

Het aanroepen van een uitzondering in de constructor kan leiden tot resource leaks als een deel van de objecten of resources binnen de constructor succesvol zijn toegewezen voordat de uitzondering wordt gegooid, en de bijbehorende destructor niet wordt aangeroepen. Om leaks te voorkomen, wordt aanbevolen RAII (Resource Acquisition Is Initialization) te gebruiken, bijvoorbeeld met slimme pointers of wrapper-klassen die het vrijgeven van resources garanderen bij het verlaten van de scope.

Het aanroepen van een uitzondering in de destructor leidt tot ongedefinieerd gedrag. Als de destructor wordt aangeroepen als gevolg van een andere uitzondering (stack unwinding), en deze zelf een onbehandelde uitzondering gooit, zal het programma eindigen met een std::terminate. Volgens de C++-standaard moeten destructors noexcept zijn.

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

struct Resource {
    Resource() { std::cout << "Resource acquired\n"; }
    ~Resource() { std::cout << "Resource released\n"; }
};

struct BadConstructor {
    Resource res; // Resource wordt toegewezen voordat de uitzondering wordt gegooid
    BadConstructor() {
        std::cout << "BadConstructor constructor\n";
        throw std::runtime_error("Error in constructor"); // Gooien van uitzondering
        // Destructor van Resource wordt niet aangeroepen als de uitzondering wordt gegooid voordat de constructor voltooid is
    }
    ~BadConstructor() noexcept { // Destructors moeten noexcept zijn
        std::cout << "BadConstructor destructor\n";
    }
};

struct GoodConstructor {
    std::unique_ptr<Resource> res; // Gebruik van slimme pointer voor RAII
    GoodConstructor() : res(std::make_unique<Resource>()) {
        std::cout << "GoodConstructor constructor\n";
        // Als er nu een uitzondering wordt gegooid, garandeert unique_ptr dat de destructor van Resource wordt aangeroepen bij het verlaten van de scope
        // throw std::runtime_error("Error in constructor");
    }
    ~GoodConstructor() noexcept {
        std::cout << "GoodConstructor destructor\n";
    }
};

struct BadDestructor {
    BadDestructor() { std::cout << "BadDestructor constructor\n"; }
    ~BadDestructor() {
        std::cout << "BadDestructor destructor\n";
        // Het is niet toegestaan om een uitzondering te gooien uit een destructor, vooral niet tijdens unwinding
        // throw std::runtime_error("Error in destructor");
    }
};

int main() {
    // Voorbeeld met constructor (leidt tot resource leak zonder RAII)
    try {
        // BadConstructor bc; // Uit commentaar halen voor demonstratie
    } catch (const std::exception& e) {
        std::cout << "Gevangen uitzondering: " << e.what() << "\n";
    }

    // Voorbeeld met goede constructor (gebruikt RAII)
    try {
        // GoodConstructor gc; // Uit commentaar halen, geen leak
    } catch (const std::exception& e) {
        std::cout << "Gevangen uitzondering: " << e.what() << "\n";
    }

    // Voorbeeld met destructor (kan leiden tot std::terminate)
    try {
        // BadDestructor bd; // Uit commentaar halen als je een potentieel terminate wilt zien
        // throw std::runtime_error("Another exception"); // Exception die unwinding veroorzaakt
    } catch (const std::exception& e) {
        std::cout << "Gevangen uitzondering: " << e.what() << "\n";
    }

    return 0;
}

Samenvattingstabel:

Plaats van uitzondering Potentiële problemen Aanbevolen oplossing
Constructor Resource leak Gebruik RAII (slimme pointers, wrappers)
Destructor Onbepaald gedrag, std::terminate tijdens unwinding Destructors moeten noexcept zijn, vermijden dat ze uitzonderingen gooien