This is an old revision of the document!
Table of Contents
Εξαιρέσεις που παράγονται στον κατασκευαστή
Ας υποθέσουμε ότι έχουμε την παρακάτω κλάση Name η οποία περιγράφει ένα όνομα
- Name.hpp
#include <cstring> class Name { char* name; public: Name(); Name(const char *name); ~Name(); Name& operator=(const Name&); }; Name::Name() { cout << "Name default constructor." << endl; name = nullptr; } Name::Name(const char *name) { cout << "Name constructor: " << name << endl; this->name = strdup(name); } Name::~Name() { cout << "Name destructor: " << name << endl; free(name); name = nullptr; } Name& Name::operator=(const Name& a) { if(name != nullptr) free(name); name = strdup(a.name); return *this; }
και την κλάση Person η οποία περιγράφει έναν άνθρωπο. Η κλάση διαθέτει τα εξής δύο πεδία:
Name* firstname; // pointer Name lastname;
Επιπλέον ο κατασκευαστή της κλάση Person παράγει ένα exception τύπου BadName. Η κλάσεις BadName και Person περιγράφονται παρακάτω:
- BadName.hpp
class BadName : public std::exception { public: const char* what() { return "BadName"; } };
- Person.hpp
#include "Name.hpp" #include "BadName.hpp" class Person { protected: Name* firstname; Name lastname; public: Person(const char *first, const char *last); ~Person(); }; Person::Person(const char *first, const char *last) { cout << "Person constructor" << endl; firstname = new Name(first); lastname = Name(last); BadName bad; throw bad; } Person::~Person() { cout << "Person destructor" << endl; delete firstname; }
Εάν μεταγλωττίσουμε και εκτελέσουμε την παρακάτω συνάρτηση main ο κώδικας εκτυπώνει τα εξής:
- main.cpp
#include <iostream> using namespace std; #include "Person.hpp" int main() { try { Person johnSnow("John", "Snow"); } catch(BadName& a) { cout << "Exception occured: " << a.what() << endl; } }
Name default constructor. Person constructor Name constructor: John Name constructor: Snow Name destructor: Snow Name destructor: Snow Occured: BadName
Από τις εκτυπώσεις, παρατηρούμε ότι καταστρέφεται το αντικείμενο lastname, αλλά όχι και το αντικείμενο firstname. Ο λόγος είναι ότι δημιουργηθεί ένα exception στον κατασκευαστή, δεν καλείται ποτέ ο καταστροφέας της κλάσης. Καταστρέφονται αυτόματα όλα τα αντικείμενα που έχουν δεσμευτεί στο stack, και λαμβάνει χώρα η διαδικασία του stack unwinding.
Ως εκ τούτου, δεν καταστρέφονται τα αντικείμενα που είναι δεσμευμένα στο heap και γενικότερα resources που περιμένουμε να απελευθερωθούν στον καταστροφέα της κλάσης.
Ο λόγος που εκτυπώνεται 2 φορές το μήνυμα “Name destructor: Snow”, είναι διότι δημιουργούνται δύο αντικείμενα τύπου Name, ένα κατά την αρχικοποίηση του αντικειμένου Person και ένα στον κατασκευαστή της κλάσης στη γραμμή lastname = Name(last);
.
Function Try Blocks
Προκειμένου να λυθεί το συγκεκριμένο πρόβλημα η C++ υποστηρίζει τα λεγόμενα function try blocks. Ουσιαστικά πρόκειται για μία ειδική σύνταξη try - catch που περιλαμβάνει το σύνολο του κατασκευαστή, ως εξής:
Person::Person(const char *first, const char *last) try { cout << "Person constructor" << endl; firstname = new Name(first); lastname = Name(last); BadName bad; throw bad; } catch(BadName& ex) { cout << "--> firstname deleted!" << endl; delete firstname; }
Εάν ανανεώσετε τον κώδικα του κατασκευαστή τώρα το πρόγραμμα σας κατά την εκτέλεση εκτυπώνει:
Name default constructor. Person constructor Name constructor: John Name constructor: Snow Name destructor: Snow Name destructor: Snow --> firstname deleted! Name destructor: John Occured: BadName
Παρατηρήστε ότι αν και εκτελείται ο κώδικας μέσα στο catch block, η εξαίρεση “μεταφέρεται” και στη συνάρτηση main/. Ο λόγος είναι ότι η εξαίρεση, αναπαράγεται αυτόματα στο τέλος του catch block. Ο μόνος τρόπος να το αποφύγετε αυτό είναι να κάνετε throw μία νέα εξαίρεση, διαφορετικού τύπου.
Exception που συμβαίνουν στον κατασκευαστή της προγόνου κλάσης
Ας υποθέσουμε την κλάση Student που είναι απόγονος της κλάσης Person και δηλώνεται ως εξής:
- Student.hpp
class Student: public Person { int aem; public: Student(const char *first, const char *last, int id); }; Student::Student(const char *first, const char *last, int id): Person(first, last), aem(id) { cout << "Student constructor" << endl; }
Εάν επιθυμούμε να “πιάσουμε” την εξαίρεση εντός του κατασκευαστή της κλάσης Student θα πρέπει να κρατήσουμε τον κατασκευαστή της κλάσης Person στην αρχική του μορφή και να ξαναγράψουμε τον κατασκευαστή:
- Student.hpp
class Student: public Person { int aem; public: Student(const char *first, const char *last, int id); }; Student::Student(const char *first, const char *last, int id) try : Person(first, last), aem(id) { cout << "Student constructor" << endl; } catch(BadName& ex) { cout << "--> firstname deleted!" << endl; delete firstname; }
Η παραπάνω σύνταξη επιτρέπει να πιάσουμε το exception που συμβαίνει στον κατασκευαστή της προγόνου κλάσης. Και εδώ το exception θα αναπαραχθεί σύμφωνα με τους κανόνες που ίσχυσαν και στο προηγούμενο παράδειγμα. Παρατηρήστε ότι δεν εκτυπώνεται ποτέ το μήνυμα “Student constructor” του κατασκευαστή. Γιατί?
Η μέθοδος main δίνεται παρακάτω:
- main.cpp
#include <iostream> using namespace std; #include "Person.hpp" #include "Student.hpp" int main() { try { Student johnSnow("John", "Snow", 1234); } catch(BadName& a) { cout << "Exception occured: " << a.what() << endl; } }