====== Κληρονομικότητα εξαιρέσεων ====== Ας υποθέσουμε ότι έχουμε τη σχέση κληρονομικότητας μεταξύ των κλάσεων **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 δεν εκτελείται ποτέ.