cpp:exception
Differences
This shows you the differences between two versions of the page.
Next revision | Previous revisionNext revisionBoth sides next revision | ||
cpp:exception [2018/05/18 11:45] – created gthanos | cpp:exception [2018/05/21 07:00] – [Ένα 2ο παράδειγμα] gthanos | ||
---|---|---|---|
Line 4: | Line 4: | ||
<code cpp Vector.cpp> | <code cpp Vector.cpp> | ||
+ | #include < | ||
+ | #include < | ||
+ | |||
+ | using namespace std; | ||
+ | |||
class Vector { | class Vector { | ||
int *array; | int *array; | ||
- | | + | int size; |
| | ||
public: | public: | ||
- | Vector(unsigned | + | Vector(int length=0); |
~Vector(); | ~Vector(); | ||
- | int & | + | int & |
}; | }; | ||
- | Vector:: | + | Vector:: |
size = length; | size = length; | ||
array = new (nothrow) int[size]; | array = new (nothrow) int[size]; | ||
Line 29: | Line 34: | ||
} | } | ||
- | int & | + | int & |
if(pos> | if(pos> | ||
cerr << " | cerr << " | ||
Line 41: | Line 46: | ||
<code cpp VectorUse.cpp> | <code cpp VectorUse.cpp> | ||
+ | #include " | ||
+ | |||
int main() { | int main() { | ||
int size; | int size; | ||
Line 54: | Line 61: | ||
<code cpp Vector.cpp> | <code cpp Vector.cpp> | ||
+ | #include < | ||
+ | #include < | ||
+ | |||
+ | using namespace std; | ||
+ | |||
class Vector { | class Vector { | ||
int *array; | int *array; | ||
- | | + | int size; |
| | ||
public: | public: | ||
- | Vector(unsigned | + | Vector(int length=0); |
~Vector(); | ~Vector(); | ||
- | int & | + | int & |
}; | }; | ||
- | Vector:: | + | Vector:: |
size = length; | size = length; | ||
- | array = new (nothrow) | + | array = new int[size]; |
for(int i=0; i<size; i++) | for(int i=0; i<size; i++) | ||
array[i] = 0; | array[i] = 0; | ||
Line 75: | Line 87: | ||
} | } | ||
- | int & | + | int & |
- | if(pos>=length()) { | + | if(pos>=size) { |
cerr << " | cerr << " | ||
| | ||
Line 85: | Line 97: | ||
<code cpp VectorUse.cpp> | <code cpp VectorUse.cpp> | ||
+ | #include " | ||
+ | |||
int main() { | int main() { | ||
int size; | int size; | ||
Line 101: | Line 115: | ||
} | } | ||
</ | </ | ||
+ | |||
+ | <WRAP tip 80% center round> | ||
+ | Αν και στο παραπάνω απλό παράδειγμα είναι προφανές ότι είναι πιο απλό να ελέγξει κανείς το μέγεθος της παραμέτρου size πριν καλέσει τον κατασκευαστή, | ||
+ | </ | ||
+ | ===== Τύποι παραγόμενων εξαιρέσεων ===== | ||
+ | |||
+ | Στη C++ μπορείτε να δημιουργήσετε ένα Exception χρησιμοποιώντας οποιονδήποτε τύπο δεδομένων, | ||
+ | |||
+ | <code cpp> | ||
+ | throw -1; // throw an integer value | ||
+ | throw ENUM_INVALID_INDEX; | ||
+ | throw " | ||
+ | double pi=3.14159; throw pi; // throw a double variable that was previously defined | ||
+ | throw MyException(" | ||
+ | </ | ||
+ | |||
+ | ===== Δημιουργία και διαχείριση της εξαίρεσης ===== | ||
+ | |||
+ | Όπως σε όλες τις γλώσσες αντικειμενοστραφούς προγραμματισμού η παραγωγή μιας εξαίρεσης θα πρέπει να γίνει μέσα σε ένα //try block// και η διαχείριση της μέσα σε ένα //catch block// που ακολουθεί το //try block//. Δείτε το παρακάτω ενδεικτικό παράδειγμα, | ||
+ | |||
+ | <code cpp ExceptionHandling.cpp> | ||
+ | #include < | ||
+ | using namespace std; | ||
+ | |||
+ | class MyException: | ||
+ | public: | ||
+ | const char* what() const throw() { | ||
+ | return "Just another std:: | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | int main() { | ||
+ | try { | ||
+ | int option; | ||
+ | cout << "Enter option (1-5): "; | ||
+ | cin >> option; | ||
+ | short int c; | ||
+ | MyException ex; | ||
+ | switch(option) { | ||
+ | case 1: | ||
+ | throw 10; // throw an int literal | ||
+ | break; | ||
+ | case 2: | ||
+ | throw 2.5; //throw a double literal | ||
+ | break; | ||
+ | case 3: | ||
+ | throw " | ||
+ | break; | ||
+ | case 4: | ||
+ | throw string(" | ||
+ | break; | ||
+ | case 5: | ||
+ | throw ex; //throw a MyException object | ||
+ | break; | ||
+ | default: | ||
+ | c = -10; throw c; // throw a character (default option) | ||
+ | break; | ||
+ | } | ||
+ | } catch(int ex) { | ||
+ | cout << "Got '"<< | ||
+ | } catch(double ex) { | ||
+ | cout << "Got '"<< | ||
+ | } catch(const char *ex) { | ||
+ | cout << "Got char* '"<< | ||
+ | } catch(const string &ex) { | ||
+ | cout << "Got string '"<< | ||
+ | } catch(const MyException &ex) { | ||
+ | cout << "Got '"<< | ||
+ | } catch(...) { // catch any exception not caught above! | ||
+ | cout << "Got an exception of unknown type!\n"; | ||
+ | } | ||
+ | cout << " | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | <WRAP info 80% center round> | ||
+ | Στον παραπάνω κώδικα το //catch block// | ||
+ | <code cpp> | ||
+ | } catch(...) { // catch any exception not caught above! | ||
+ | cout << "Got an exception of unknown type!\n"; | ||
+ | } | ||
+ | </ | ||
+ | πιάνει όλους τους τύπους // | ||
+ | </ | ||
+ | |||
+ | <WRAP tip 80% center round> | ||
+ | Στον παραπάνω κώδικα μπορείτε να παρατηρήσετε τα διαφορετικά μηνύματα που παράγονται ανάλογα με τον τύπο της εξαίρεσης. Παρατηρήστε επίσης ότι αν και παράγεται ένα αντικείμενο τύπου //short int//, το οποίο χωράει σε ένα //int// δεν γίνεται κάποια αυτόματη μετατροπή τύπου, ώστε το //catch block// που πιάνει τύπους //int// να πιάσει και αντικείμενα τύπου short int. | ||
+ | </ | ||
+ | |||
+ | ===== Κληρονομικότητα εξαιρέσεων ===== | ||
+ | |||
+ | Ας υποθέσουμε ότι έχουμε τη σχέση κληρονομικότητας μεταξύ των κλάσεων **BaseException** και **DerivedException**, | ||
+ | |||
+ | <code cpp BaseException.h> | ||
+ | using namespace std; | ||
+ | |||
+ | class BaseException: | ||
+ | protected: | ||
+ | int a; | ||
+ | public: | ||
+ | BaseException(int a) { this->a = a; } | ||
+ | const char* what() const throw() { | ||
+ | //char s[64]; | ||
+ | char *s = new char [64]; | ||
+ | sprintf(s, " | ||
+ | return s; | ||
+ | } | ||
+ | }; | ||
+ | </ | ||
+ | |||
+ | <code cpp DerivedException.h> | ||
+ | #include " | ||
+ | using namespace std; | ||
+ | |||
+ | class DerivedException: | ||
+ | int b; | ||
+ | public: | ||
+ | DerivedException(int a, int b): BaseException(a) { this->b = b; } | ||
+ | const char* what() const throw() { | ||
+ | char *s = new char [64]; | ||
+ | sprintf(s, " | ||
+ | return s; | ||
+ | } | ||
+ | }; | ||
+ | </ | ||
+ | |||
+ | <code cpp ExceptionUse.cpp> | ||
+ | #include < | ||
+ | #include " | ||
+ | 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.what(); | ||
+ | } catch(DerivedException ex) { | ||
+ | cout << ex.what(); | ||
+ | } | ||
+ | return 0; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | O παραπάνω κώδικας παράγει το παρακάτω //warning// κατά τη μεταγλώττιση: | ||
+ | < | ||
+ | ExceptionUse.cpp: | ||
+ | } catch(DerivedException &ex) { | ||
+ | ^ | ||
+ | ExceptionUse.cpp: | ||
+ | } catch(BaseException &ex) { | ||
+ | ^ | ||
+ | </ | ||
+ | |||
+ | το οποίο εν συντομία λέει ότι ένα exception τύπου // | ||
+ | |||
+ | Εκτελέστε όμως τον παραπάνω κώδικα (παρά το warning) δίνοντας ορίσμα τους αριθμούς 1 και 2. Το αποτέλεσμα είναι το εξής: | ||
+ | < | ||
+ | gthanos@gthanos-DESKTOP: | ||
+ | Enter option (1-2): 1 | ||
+ | BaseException, | ||
+ | gthanos@gthanos-DESKTOP: | ||
+ | Enter option (1-2): 2 | ||
+ | BaseException, | ||
+ | </ | ||
+ | |||
+ | Παρατηρήστε ότι ενώ στην 2η περίπτωση παράγεται ένα // | ||
+ | |||
+ | Η απάντηση στο παραπάνω ερώτημα είναι ότι αν και παράγεται ένα αντικείμενο τύπου // | ||
+ | |||
+ | Ο τρόπος για να δουλέψει σωστά ο παραπάνω κώδικας είναι μέσα στο //catch block// να μην περάσουμε το αντικείμενο γιατί δημιουργείται αντίγραφο, | ||
+ | |||
+ | <code cpp ExceptionUse.cpp> | ||
+ | #include < | ||
+ | #include " | ||
+ | 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.what(); | ||
+ | } catch(DerivedException &ex) { | ||
+ | cout << ex.what(); | ||
+ | } | ||
+ | return 0; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Πλέον το αποτέλεσμα της εκτέλεσης είναι το αναμενόμενο | ||
+ | < | ||
+ | gthanos@gthanos-DESKTOP: | ||
+ | Enter option (1-2): 1 | ||
+ | BaseException, | ||
+ | gthanos@gthanos-DESKTOP: | ||
+ | Enter option (1-2): 2 | ||
+ | DerivedException, | ||
+ | </ | ||
+ | |||
+ | <WRAP tip 80% center round> | ||
+ | Το πιάσιμο μιας εξαίρεσης με χρήση αναφοράς για αντικείμενα σύνθετου τύπου (όχ char, int, long, double κλπ), διότι //α)// αποφεύγουμε την αντιγραφή του αντικειμένου μέσα στο //catch block// (πιο γρήγορος κώδικας) και //β)// αποφεύγουμε την " | ||
+ | </ | ||
+ | |||
+ | <WRAP tip 80% center round> | ||
+ | Είναι προφανές ότι η σειρά των //catch blocks// θα έπρεπε να είναι η αντίστροφη (πρώτα το //catch block// για την αναφορά τύπου // | ||
+ | </ | ||
+ | |||
+ | ===== Stack Unwinding ===== | ||
+ | |||
+ | Κατά την δημιουργία ενός // | ||
+ | |||
+ | <code cpp StackUnwinding.cpp> | ||
+ | #include < | ||
+ | // called by FFF() | ||
+ | void FFFF() { | ||
+ | std::cout << "Start FFFF\n"; | ||
+ | std::cout << "FFFF throwing int literal exception\n"; | ||
+ | throw 100; | ||
+ | std::cout << "End FFFF\n"; | ||
+ | |||
+ | } | ||
+ | // called by FF() | ||
+ | void FFF() { | ||
+ | std::cout << "Start FFF\n"; | ||
+ | FFFF(); | ||
+ | std::cout << "End FFF\n"; | ||
+ | } | ||
+ | // called by F() | ||
+ | void FF() { | ||
+ | std::cout << "Start FF\n"; | ||
+ | try { | ||
+ | FFF(); | ||
+ | } catch(char) { | ||
+ | std::cerr << "FF caught double exception\n"; | ||
+ | } | ||
+ | std::cout << "End FF\n"; | ||
+ | } | ||
+ | // called by main() | ||
+ | void F() { | ||
+ | std::cout << "Start F\n"; | ||
+ | try { | ||
+ | FF(); | ||
+ | } catch (int) { | ||
+ | | ||
+ | } catch (char) { | ||
+ | | ||
+ | } | ||
+ | std::cout << "End F\n"; | ||
+ | } | ||
+ | |||
+ | int main() { | ||
+ | std::cout << "Start main\n"; | ||
+ | try { | ||
+ | F(); | ||
+ | } catch (int) { | ||
+ | std::cerr << "main caught int exception\n"; | ||
+ | } | ||
+ | std::cout << "End main\n"; | ||
+ | return 0; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Το αποτέλεσμα που εκτυπώνεται στην οθόνη είναι το εξής: | ||
+ | < | ||
+ | Start main | ||
+ | Start F | ||
+ | Start FF | ||
+ | Start FFF | ||
+ | Start FFFF | ||
+ | FFFF throwing int literal exception | ||
+ | F caught int exception | ||
+ | End F | ||
+ | End main | ||
+ | </ | ||
+ | |||
+ | H εξέλιξη του //program stack// στο παρακάτω πρόγραμμα δίνεται στο παρακάτω διάγραμμα. Παρατηρήστε ότι το exception παράγεται στη συνάρτηση **FFFF()**, αλλά " | ||
+ | |||
+ | {{ : | ||
+ | |||
+ | ===== Διαχείριση μίας εξαίρεσης και παραγωγή νέας εξαίρεσης κατά την διαχείριση της ===== | ||
+ | |||
+ | Κάποιες φορές είναι επιθυμητό να διαχειριστούμε μία εξαίρεση προκειμένου να κλείσουμε κάποιο // | ||
+ | |||
+ | <code cpp PPMImageSample.cpp> | ||
+ | #include < | ||
+ | #include < | ||
+ | #include <ios> | ||
+ | #include < | ||
+ | |||
+ | using namespace std; | ||
+ | |||
+ | class PPMImage { | ||
+ | int width, height, colordepth; | ||
+ | int **raster; | ||
+ | public: | ||
+ | PPMImage(char *filename) { | ||
+ | string str; | ||
+ | unsigned char red, green, blue; | ||
+ | ifstream in(filename); | ||
+ | if(!in.is_open()) { | ||
+ | std:: | ||
+ | throw fex; | ||
+ | } | ||
+ | try { | ||
+ | in >> str; | ||
+ | in >> str; | ||
+ | width = atoi(str.c_str()); | ||
+ | in >> str; | ||
+ | height = atoi(str.c_str()); | ||
+ | in >> str; | ||
+ | colordepth = atoi(str.c_str()); | ||
+ | raster = new int*[height]; | ||
+ | for(int row=0; row< | ||
+ | raster[row] = new int[width]; | ||
+ | for(int row=0; row< | ||
+ | for(int col=0; col< | ||
+ | cin >> str; | ||
+ | red = (unsigned char) atoi(str.c_str()); | ||
+ | cin >> str; | ||
+ | green = (unsigned char) atoi(str.c_str()); | ||
+ | cin >> str; | ||
+ | blue = (unsigned char) atoi(str.c_str()); | ||
+ | raster[row][col] = 0; | ||
+ | raster[row][col] = (red << 16) | (green << 8) | blue; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | catch(std:: | ||
+ | cerr << " | ||
+ | in.close(); | ||
+ | throw ex; | ||
+ | } | ||
+ | } | ||
+ | | ||
+ | ~PPMImage() { | ||
+ | for(int row=0; row< | ||
+ | delete raster[row]; | ||
+ | delete raster; | ||
+ | } | ||
+ | | ||
+ | int **getRaster() { return raster; } | ||
+ | | ||
+ | }; | ||
+ | |||
+ | int main(int argc, char *argv[]) { | ||
+ | PPMImage *imgptr=nullptr; | ||
+ | try{ | ||
+ | imgptr = new PPMImage(argv[1]); | ||
+ | } | ||
+ | catch(ios_base:: | ||
+ | cerr << "File '" | ||
+ | } | ||
+ | catch(bad_alloc &ex) { | ||
+ | cerr << " | ||
+ | if (imgptr!=nullptr) { | ||
+ | cerr << " | ||
+ | if(imgptr-> | ||
+ | cerr << " | ||
+ | delete imgptr-> | ||
+ | } | ||
+ | else { | ||
+ | cerr << " | ||
+ | } | ||
+ | delete imgptr; | ||
+ | } | ||
+ | else { | ||
+ | cerr << " | ||
+ | } | ||
+ | } | ||
+ | delete imgptr; | ||
+ | return 0; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Το ενδεικτικό αρχείο εισόδου είναι το παρακάτω: | ||
+ | |||
+ | <code cpp 3x2.ppm> | ||
+ | P3 | ||
+ | 3 -2 255 | ||
+ | 255 0 0 255 255 0 0 255 255 | ||
+ | 255 0 255 0 255 0 128 128 128 | ||
+ | </ | ||
+ | |||
+ | Από τον παραπάνω κώδικα μπορούμε να συμπεράνουμε τα εξής: | ||
+ | - Εφόσον παράγεται ένα // | ||
+ | - Εάν εφαρμόσω τον τελεστή **delete** σε ένα δείκτη που έχει την τιμή **nullptr**, | ||
+ | |||
+ | ==== Ένα 2ο παράδειγμα ==== | ||
+ | |||
+ | Στο προηγούμενο παράδειγμα κάντε την εξής αλλαγή. Αντικαταστήστε το //catch block// στον κατασκευαστή με το παρακάτω: | ||
+ | |||
+ | <code cpp> | ||
+ | catch(std:: | ||
+ | cerr << " | ||
+ | in.close(); | ||
+ | throw ex; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Το //exception std:: | ||
+ | < | ||
+ | $> ./ | ||
+ | std:: | ||
+ | terminate called after throwing an instance of ' | ||
+ | what(): | ||
+ | Aborted (core dumped) | ||
+ | </ | ||
+ | |||
+ | Ο λόγος που συμβαίνει το παραπάνω είναι ότι το όταν το //catch block// παράγει και πάλι το // | ||
+ | |||
+ | <code cpp> | ||
+ | catch(std:: | ||
+ | cerr << " | ||
+ | in.close(); | ||
+ | throw; | ||
+ | } | ||
+ | </ | ||
+ | ===== Δήλωση των εξαιρέσεων που παράγουν οι συναρτήσεις (exception specifiers) ===== | ||
+ | |||
+ | Κατά τη δήλωση μίας συνάρτησης είναι δυνατόν να ορίσετε εάν αυτή μπορεί να παράγει κάποιου είδους // | ||
+ | - '' | ||
+ | - '' | ||
+ | - '' | ||
+ | |||
+ | Τους παραπάνω ορισμούς είναι δυνατόν να τους συναντήσετε στις // | ||
+ | |||
+ | /* | ||
+ | ===== Function try blocks ===== | ||
+ | */ | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | |||
+ | |||
+ | |||
cpp/exception.txt · Last modified: 2023/05/15 14:01 by gthanos