====== Εξαιρέσεις που παράγονται στον κατασκευαστή ======
Ας υποθέσουμε ότι έχουμε την παρακάτω κλάση //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;
}
}