Sobes.tech
Middle+

Αναφέρετε τις προκλήσεις της ρίψης εξαιρέσεων (throw exception) στους κατασκευαστές και τους καταστροφείς.

sobes.tech AI

Απάντηση από AI

Η κλήση εξαίρεσης στον κατασκευαστή μπορεί να οδηγήσει σε διαρροή πόρων εάν κάποιοι πόροι ή αντικείμενα έχουν ήδη δεσμευτεί πριν από την εξαίρεση, και ο αντίστοιχος καταστροφέας δεν καλείται. Για την αποφυγή διαρροών, συνιστάται η χρήση RAII (Resource Acquisition Is Initialization), όπως έξυπνοι δείκτες ή περιτυλιγμένες κλάσεις που διασφαλίζουν την απελευθέρωση πόρων.

Η κλήση εξαίρεσης στον καταστροφέα οδηγεί σε απροσδιόριστη συμπεριφορά. Αν ο καταστροφέας καλείται ως αποτέλεσμα άλλης εξαίρεσης (stack unwinding), και ο ίδιος ρίξει μη επεξεργασμένη εξαίρεση, το πρόγραμμα θα τερματίσει με std::terminate. Σύμφωνα με το πρότυπο C++, οι καταστροφείς πρέπει να είναι noexcept.

#include <iostream>
#include <memory> // Για std::unique_ptr

struct Resource {
    Resource() { std::cout << "Πόρος αποκτήθηκε\n"; }
    ~Resource() { std::cout << "Πόρος απελευθερώθηκε\n"; }
};

struct BadConstructor {
    Resource res; // Πόρος θα δεσμευτεί πριν από την εξαίρεση
    BadConstructor() {
        std::cout << "Κατασκευαστής BadConstructor\n";
        throw std::runtime_error("Σφάλμα στον κατασκευαστή"); // Ρίχνει εξαίρεση
        // Ο καταστροφέας Resource δεν θα κληθεί αν η εξαίρεση ριχτεί πριν από το τέλος του κατασκευαστή
    }
    ~BadConstructor() noexcept { // Οι καταστροφείς πρέπει να είναι noexcept
        std::cout << "Καταστροφέας BadConstructor\n";
    }
};

struct GoodConstructor {
    std::unique_ptr<Resource> res; // Χρήση έξυπνου δείκτη για RAII
    GoodConstructor() : res(std::make_unique<Resource>()) {
        std::cout << "Κατασκευαστής GoodConstructor\n";
        // Αν ριχτεί εξαίρεση, το unique_ptr διασφαλίζει την κλήση του καταστροφέα του Resource κατά την έξοδο από το scope
        // throw std::runtime_error("Σφάλμα στον κατασκευαστή");
    }
    ~GoodConstructor() noexcept {
        std::cout << "Καταστροφέας GoodConstructor\n";
    }
};

struct BadDestructor {
    BadDestructor() { std::cout << "Κατασκευαστής BadDestructor\n"; }
    ~BadDestructor() {
        std::cout << "Καταστροφέας BadDestructor\n";
        // Δεν πρέπει να ρίχνει εξαίρεση από τον καταστροφέα, ειδικά κατά το unwind
        // throw std::runtime_error("Σφάλμα στον καταστροφέα");
    }
};

int main() {
    // Παράδειγμα με κατασκευαστή (θα οδηγήσει σε διαρροή πόρων χωρίς RAII)
    try {
        // BadConstructor bc; // Αποσχολιάστε για να δείτε τη διαρροή
    } catch (const std::exception& e) {
        std::cout << "Πιάστηκε εξαίρεση: " << e.what() << "\n";
    }

    // Παράδειγμα με καλό κατασκευαστή (χρησιμοποιεί RAII)
    try {
        // GoodConstructor gc; // Αποσχολιάστε για να δείτε, δεν θα υπάρχει διαρροή
    } catch (const std::exception& e) {
        std::cout << "Πιάστηκε εξαίρεση: " << e.what() << "\n";
    }

    // Παράδειγμα με καταστροφέα (μπορεί να οδηγήσει σε std::terminate)
    try {
        // BadDestructor bd; // Αποσχολιάστε αν θέλετε να δείτε το πιθανό terminate
        // throw std::runtime_error("Άλλη εξαίρεση"); // θα προκαλέσει unwind
    } catch (const std::exception& e) {
        std::cout << "Πιάστηκε εξαίρεση: " << e.what() << "\n";
    }

    return 0;
}

Πίνακας:

Τοποθεσία κλήσης εξαίρεσης Πιθανά προβλήματα Συνιστώμενη λύση
Κατασκευαστής Διαρροή πόρων Χρησιμοποιήστε RAII (έξυπνοι δείκτες, περιτυλιγμένες κλάσεις)
Καταστροφέας Απροσδιόριστη συμπεριφορά, std::terminate κατά το unwind Οι καταστροφείς πρέπει να είναι noexcept, αποφεύγετε την ρίψη εξαίρεσης