====== Κληρονομικότητα εξαιρέσεων ======
Ας υποθέσουμε ότι έχουμε τη σχέση κληρονομικότητας μεταξύ των κλάσεων **BaseException** και **DerivedException**, όπως παρακάτω:
using namespace std;
class BaseException {
protected:
int a;
char s[64];
public:
BaseException(int a) { this->a = a; }
virtual char* message() {
sprintf(s, "BaseException, a: %d\n", a);
return s;
}
};
#include "BaseException.hpp"
using namespace std;
class DerivedException: public BaseException {
int b;
public:
DerivedException(int a, int b): BaseException(a) { this->b = b; }
char* message() {
sprintf(s, "DerivedException, a: %d, b: %d\n", a, b);
return s;
}
};
#include
#include "DerivedException.hpp"
using namespace std;
int main() {
try {
int option;
cout << "Enter option (1-2): ";
cin >> option;
BaseException baseEx(-2);
DerivedException derivedEx(4,5);
switch(option) {
case 1:
throw baseEx;
break;
case 2:
throw derivedEx;
break;
}
} catch(BaseException ex) {
cout << ex.message();
} catch(DerivedException ex) {
cout << ex.message();
}
return 0;
}
O παραπάνω κώδικας παράγει το παρακάτω //warning// κατά τη μεταγλώττιση:
ExceptionUse.cpp:22:5: warning: exception of type ‘DerivedException’ will be caught
} catch(DerivedException &ex) {
^
ExceptionUse.cpp:20:5: warning: by earlier handler for ‘BaseException’
} catch(BaseException &ex) {
^
το οποίο εν συντομία λέει ότι ένα exception τύπου //DerivedException// θα "πιαστεί" από το 1ο //catch block// και το 2ο //catch block// δεν θα λειτουργήσει ποτέ. Το παραπάνω είναι λογικό διότι ένα αντικείμενο της κλάσης //DerivedException// είναι και //BaseException// με βάση τις αρχές της κληρονομικότητας.
Εκτελέστε όμως τον παραπάνω κώδικα (παρά το warning) δίνοντας ορίσμα τους αριθμούς 1 και 2. Το αποτέλεσμα είναι το εξής:
gthanos@gthanos-DESKTOP:~/Downloads/C++$ ./ExceptionUse
Enter option (1-2): 1
BaseException, a: -2
gthanos@gthanos-DESKTOP:~/Downloads/C++$ ./ExceptionUse
Enter option (1-2): 2
BaseException, a: 4
Παρατηρήστε ότι ενώ στην 2η περίπτωση παράγεται ένα //DerivedException// το αντικείμενο που τελικά λαμβάνουμε είναι τύπου //BaseException//. Εδώ θα πρέπει να τονίσουμε ότι η συνάρτηση //message()// είναι //virtual// πράγμα που διαισθητικά μας οδηγεί στο συμπέρασμα ότι θα κληθεί η κατάλληλη έκδοση της συνάρτησης με βάση τον τύπο του αντικειμένου για το οποίο καλείται, ανεξάρτητα από τον τύπο της (δες [[cpp:polymorphism|δυναμικό πολυμορφισμό]]).
Η απάντηση στο παραπάνω ερώτημα είναι ότι αν και παράγεται ένα αντικείμενο τύπου //DerivedException// αυτό γίνεται //catch// από το πρώτο //catch block//. Μέσα στο //catch block// το αρχικό αντικείμενο αντιγράφεται σε ένα άλλο αντικείμενο τύπου //BaseException//, διότι έχουμε κλήση με τιμή στο //catch block//. Πρακτικά αυτό σημαίνει ότι από το αρχικό αντικείμενο κρατάμε οτιδήποτε ανήκει στην κλάση //BaseException// και απορρίπτουμε το υπόλοιπο.
Ο τρόπος για να δουλέψει σωστά ο παραπάνω κώδικας είναι μέσα στο //catch block// να μην περάσουμε το αντικείμενο γιατί δημιουργείται αντίγραφο του, αλλά να περάσουμε μία αναφορά σε αυτό, όπως παρακάτω:
#include
#include "DerivedException.hpp"
using namespace std;
int main() {
try {
int option;
cout << "Enter option (1-2): ";
cin >> option;
BaseException bex(-2);
DerivedException dex(4,5);
switch(option) {
case 1:
throw bex;
break;
case 2:
throw dex;
break;
}
} catch(BaseException &ex) {
cout << ex.message();
} catch(DerivedException &ex) {
cout << ex.message();
}
return 0;
}
Πλέον το αποτέλεσμα της εκτέλεσης είναι το αναμενόμενο
gthanos@gthanos-DESKTOP:~/Downloads/C++$ ./ExceptionUse
Enter option (1-2): 1
BaseException, a: -2
gthanos@gthanos-DESKTOP:~/Downloads/C++$ ./ExceptionUse
Enter option (1-2): 2
DerivedException, a: 4, b: 5
Συνιστάται, η διαχείριση μιας εξαίρεσης με χρήση αναφοράς για αντικείμενα σύνθετου τύπου (δηλαδή όχι τύπου char, int, long, double κλπ), διότι //α)// αποφεύγουμε την αντιγραφή του αντικειμένου μέσα στο //catch block// (πιο γρήγορη κλήση) και //β)// αποφεύγουμε την "αποκοπή" μέρους του αντικειμένου της εξαίρεσης λόγω του χειρισμού της από ένα //catch block// βασικότερου τύπου.
Είναι προφανές ότι η σειρά των //catch blocks// θα έπρεπε να είναι η αντίστροφη (πρώτα το //catch block// για την αναφορά τύπου //DerivedException// και στη συνέχεια το //catch block// για την αναφορά τύπου //BasedException//. Ο λόγος είναι ότι εάν παραμείνει η σειρά των //catch blocks// ως έχει το 2o catch block δεν εκτελείται ποτέ.