====== Εξαιρέσεις που παράγονται στον κατασκευαστή ====== Ας υποθέσουμε ότι έχουμε την παρακάτω κλάση //Name// η οποία περιγράφει ένα όνομα #include 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) { cout << "--- Operator= ---\n"; if(name != nullptr) free(name); name = strdup(a.name); return *this; } και την κλάση //Person// η οποία περιγράφει έναν άνθρωπο. Η κλάση διαθέτει τα εξής δύο πεδία: Name* firstname; // pointer to Name Name lastname; // Name Επιπλέον ο κατασκευαστή της κλάση Person παράγει ένα //exception// τύπου //BadName//. Η κλάσεις //Person// και //BadName// περιγράφονται παρακάτω: #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; } class BadName : public std::exception { public: const char* what() { return "BadName"; } }; Εάν μεταγλωττίσουμε και εκτελέσουμε την παρακάτω συνάρτηση //main// ο κώδικας εκτυπώνει τα εξής: #include 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 --- Operator= --- Name destructor: Snow Name destructor: Snow Exception occured: BadName Από τις εκτυπώσεις, παρατηρούμε ότι καταστρέφεται το αντικείμενο //lastname//, αλλά όχι και το αντικείμενο //firstname//. Ο λόγος είναι ότι δημιουργηθεί ένα //exception// στον κατασκευαστή, __δεν καλείται ποτέ ο καταστροφέας της κλάσης__. Καταστρέφονται αυτόματα όλα τα αντικείμενα που έχουν δεσμευτεί στο //stack//, και λαμβάνει χώρα η διαδικασία του //[[cpp:stack_unwinding|stack unwinding]]//. Ως εκ τούτου, δεν καταστρέφονται τα αντικείμενα που είναι δεσμευμένα στο //heap// και γενικότερα τα //resources// που περιμένουμε να απελευθερωθούν στον καταστροφέα της κλάσης. Ο λόγος που εκτυπώνεται 2 φορές το μήνυμα **"Name destructor: Snow"**, είναι διότι δημιουργούνται δύο αντικείμενα τύπου Name, ένα κατά την αρχικοποίηση του αντικειμένου //Person// (με τη βοήθεια του //default constructor//)και ένα στον κατασκευαστή της κλάσης στη γραμμή ''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 << "\n--> firstname deleted!" << endl; delete firstname; } Εάν ανανεώσετε τον κώδικα του κατασκευαστή τώρα το πρόγραμμα σας κατά την εκτέλεση εκτυπώνει: Name default constructor. Person constructor Name constructor: John Name constructor: Snow --- Operator= --- Name destructor: Snow Name destructor: Snow --> firstname deleted! Name destructor: John Exception occured: BadName Παρατηρήστε ότι αν και εκτελείται ο κώδικας μέσα στο //catch block//, η εξαίρεση "μεταφέρεται" και στη συνάρτηση //main//. Ο λόγος είναι ότι η εξαίρεση, **αναπαράγεται αυτόματα** στο τέλος του //catch block//. Ο μόνος τρόπος να το αποφύγετε αυτό είναι να κάνετε //throw// μία νέα εξαίρεση, διαφορετικού τύπου. ===== Exception που συμβαίνουν στον κατασκευαστή της προγόνου κλάσης ===== Ας υποθέσουμε την κλάση //Student// που είναι απόγονος της κλάσης //Person// και δηλώνεται ως εξής: 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//, θα πρέπει να κρατήσουμε τον κατασκευαστή της κλάσης //Person// στην αρχική του μορφή (πριν την τελευταία αλλαγή) και να ξαναγράψουμε τον κατασκευαστή της κλάσης //Student// ως εξής: 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// δίνεται παρακάτω: #include 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; } }