User Tools

Site Tools


cpp:exception

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Next revision
Previous revision
Next revisionBoth sides next revision
cpp:exception [2018/05/18 11:45] – created gthanoscpp:exception [2018/05/20 14:26] – [Δήλωση των εξαιρέσεων που παράγουν οι συναρτήσεις (exception specifiers)] gthanos
Line 4: Line 4:
  
 <code cpp Vector.cpp> <code cpp Vector.cpp>
 +#include <iostream>
 +#include <cstdlib>
 +
 +using namespace std;
 +
 class Vector { class Vector {
   int *array;   int *array;
-  unsigned int size;+  int size;
      
 public: public:
-  Vector(unsigned int length=0);+  Vector(int length=0);
   ~Vector();   ~Vector();
-  int &valueAt(unsigned int pos) const;  // returns a reference to element at position pos+  int &valueAt(int pos) const;  // returns a reference to element at position pos
 }; };
  
-Vector::Vector(unsigned int length) {+Vector::Vector(int length) {
   size = length;   size = length;
   array = new (nothrow) int[size];   array = new (nothrow) int[size];
Line 29: Line 34:
 } }
  
-int &Vector::valueAt(unsigned int pos) const {+int &Vector::valueAt(int pos) const {
   if(pos>=length()) {   if(pos>=length()) {
      cerr << "Invalid access position!\n";      cerr << "Invalid access position!\n";
Line 41: Line 46:
  
 <code cpp VectorUse.cpp> <code cpp VectorUse.cpp>
 +#include "Vector.cpp"
 +
 int main() { int main() {
   int size;   int size;
Line 54: Line 61:
  
 <code cpp Vector.cpp> <code cpp Vector.cpp>
 +#include <iostream>
 +#include <cstdlib>
 +
 +using namespace std;
 +
 class Vector { class Vector {
   int *array;   int *array;
-  unsigned int size;+  int size;
      
 public: public:
-  Vector(unsigned int length=0);+  Vector(int length=0);
   ~Vector();   ~Vector();
-  int &valueAt(unsigned int pos) const;  // returns a reference to element at position pos+  int &valueAt(int pos) const;  // returns a reference to element at position pos
 }; };
  
-Vector::Vector(unsigned int length) {+Vector::Vector(int length) {
   size = length;   size = length;
   array = new (nothrow) int[size];   array = new (nothrow) int[size];
Line 75: Line 87:
 } }
  
-int &Vector::valueAt(unsigned int pos) const { +int &Vector::valueAt(int pos) const { 
-  if(pos>=length()) {+  if(pos>=size) {
      cerr << "Invalid access position!\n";      cerr << "Invalid access position!\n";
      return array[size-1];      return array[size-1];
Line 85: Line 97:
  
 <code cpp VectorUse.cpp> <code cpp VectorUse.cpp>
 +#include "Vector.cpp"
 +
 int main() { int main() {
   int size;   int size;
Line 101: Line 115:
 } }
 </code> </code>
 +
 +<WRAP tip 80% center round>
 +Αν και στο παραπάνω απλό παράδειγμα είναι προφανές ότι είναι πιο απλό να ελέγξει κανείς το μέγεθος της παραμέτρου size πριν καλέσει τον κατασκευαστή, κάτι τέτοιο είναι δύσκολο να εφαρμοστεί σε όλες τις περιπτώσεις, όπως για παράδειγμα το μέγεθος **size** παράγεται δυναμικά από το πρόγραμμα και ο κατασκευαστής καλείται σε αρκετά διαφορετικά σημεία του προγράμματος.
 +</WRAP>
 +===== Τύποι παραγόμενων εξαιρέσεων =====
 +
 +Στη C++ μπορείτε να δημιουργήσετε ένα Exception χρησιμοποιώντας οποιονδήποτε τύπο δεδομένων, δηλαδή δεν απαιτείται τα αντικείμενα που παράγονται να είναι απόγονοι συγκεκριμένης κλάσης. Δείτε μερικά παραδείγματα παραγωγής έγκυρων //exceptions// παρακάτω:
 +
 +<code cpp>
 +throw -1;                     // throw an integer value
 +throw ENUM_INVALID_INDEX;     // throw an enum value
 +throw "Invalid argument!";    // throw a literal C-style (const char*) string
 +double pi=3.14159; throw pi;  // throw a double variable that was previously defined
 +throw MyException("Fatal!");  // Throw an object of class MyException
 +</code>
 +
 +===== Δημιουργία και διαχείριση της εξαίρεσης =====
 +
 +Όπως σε όλες τις γλώσσες αντικειμενοστραφούς προγραμματισμού η παραγωγή μιας εξαίρεσης θα πρέπει να γίνει μέσα σε ένα //try block// και η διαχείριση της μέσα σε ένα //catch block// που ακολουθεί το //try block//. Δείτε το παρακάτω ενδεικτικό παράδειγμα, όπου ανάλογα με την είσοδο που βάζει ο χρήστης παράγεται διαφορετικού τύπου //exception//
 +
 +<code cpp ExceptionHandling.cpp>
 +#include <iostream>
 +using namespace std;
 +
 +class MyException: public std::exception {
 +public:
 +  const char* what() const throw() {
 +    return "Just another std::exception";
 +  }
 +};
 +
 +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 "C++"; //throw a char * literal
 +        break;
 +      case 4:
 +        throw string("C++"); //throw a 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 '"<< ex <<"'!\n";
 +  } catch(double ex) {
 +    cout << "Got '"<< ex <<"'!\n";
 +  } catch(const char *ex) {
 +    cout << "Got char* '"<< ex <<"'!\n";
 +  } catch(const string &ex) {
 +    cout << "Got string '"<< ex <<"'!\n";
 +  } catch(const MyException &ex) {
 +    cout << "Got '"<< ex.what() <<"'!\n";
 +  } catch(...) {    // catch any exception not caught above!
 +    cout << "Got an exception of unknown type!\n";
 +  }
 +  cout << "Successfully handled the created exception!\n";
 +}
 +</code>
 +
 +<WRAP info 80% center round>
 +Στον παραπάνω κώδικα το //catch block//
 +<code cpp>
 +  } catch(...) {     // catch any exception not caught above!
 +    cout << "Got an exception of unknown type!\n";
 +  }
 +</code>
 +πιάνει όλους τους τύπους //exception// που δεν πιάστηκαν στα προηγούμενα //catch blocks//. Τοποθετώντας ένα //catch block// αυτής της μορφής είναι δυνατόν να εφαρμόσετε ένα τελικό έλεγχο για τύπους εξαιρέσεων που δεν έχετε προβλέψει ότι μπορούν να παραχθούν παραπάνω στον κώδικα σας.
 +</WRAP>
 +
 +<WRAP tip 80% center round>
 +Στον παραπάνω κώδικα μπορείτε να παρατηρήσετε τα διαφορετικά μηνύματα που παράγονται ανάλογα με τον τύπο της εξαίρεσης. Παρατηρήστε επίσης ότι αν και παράγεται ένα αντικείμενο τύπου //short int//, το οποίο χωράει σε ένα //int// δεν γίνεται κάποια αυτόματη μετατροπή τύπου, ώστε το //catch block// που πιάνει τύπους //int// να πιάσει και αντικείμενα τύπου short int.
 +</WRAP>
 +
 +===== Κληρονομικότητα =====
 +
 +Ας υποθέσουμε ότι έχουμε τη σχέση κληρονομικότητας μεταξύ των κλάσεων **BaseException** και **DerivedException**, όπως παρακάτω:
 +
 +<code cpp BaseException.h>
 +using namespace std;
 +
 +class BaseException: public std::exception {
 +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, "BaseException, a: %d\n", a);
 +    return s;
 +  }
 +};
 +</code>
 +
 +<code cpp DerivedException.h>
 +#include "BaseException.h"
 +using namespace std;
 +
 +class DerivedException: public BaseException {
 +  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, "DerivedException, a: %d, b: %d\n", a, b);
 +    return s;
 +  }
 +};
 +</code>
 +
 +<code cpp ExceptionUse.cpp>
 +#include <iostream>
 +#include "DerivedException.h"
 +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;
 +}
 +</code>
 +
 +O παραπάνω κώδικας παράγει το παρακάτω //warning// κατά τη μεταγλώττιση:
 +<code>
 +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) {
 +     ^
 +</code>
 +
 +το οποίο εν συντομία λέει ότι ένα exception τύπου //DerivedException// θα "πιαστεί" από το 1ο //catch block// και το 2ο //catch block// δεν θα λειτουργήσει ποτέ. Το παραπάνω είναι λογικό διότι ένα αντικείμενο της κλάσης //DerivedException// είναι και //BaseException// με βάση τις αρχές της κληρονομικότητας.
 +
 +Εκτελέστε όμως τον παραπάνω κώδικα (παρά το warning) δίνοντας ορίσμα τους αριθμούς 1 και 2. Το αποτέλεσμα είναι το εξής:
 +<code>
 +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
 +</code>
 +
 +Παρατηρήστε ότι ενώ στην 2η περίπτωση παράγεται ένα //DerivedException// το αντικείμενο που τελικά λαμβάνουμε είναι τύπου //BaseException//. Εδώ θα πρέπει να τονίσουμε ότι η συνάρτηση //what()// είναι //virtual// πράγμα που σημαίνει ότι θα πρέπει να καλείται η κατάλληλη έκδοση της συνάρτησης με βάση τον τύπο του αντικειμένου για το οποίο καλείται, ανεξάρτητα από τον τύπο της η οποία δείχνει στο αντικείμενο (δες [[cpp:polymorphism|δυναμικό πολυμορφισμό]]).
 +
 +Η απάντηση στο παραπάνω ερώτημα είναι ότι αν και παράγεται ένα αντικείμενο τύπου //DerivedException// αυτό γίνεται //catch// από το πρώτο //catch block//. Μέσα στο //catch block// το αρχικό αντικείμενο αντιγράφεται σε ένα άλλο αντικείμενο τύπου //BaseException//, διότι έχουμε κλήση με τιμή στο //catch block//. Πρακτικά αυτό σημαίνει ότι από το αρχικό αντικείμενο κρατάμε οτιδήποτε ανήκει στην κλάση //BaseException// και απορρίπτουμε το υπόλοιπο.
 +
 +Ο τρόπος για να δουλέψει σωστά ο παραπάνω κώδικας είναι μέσα στο //catch block// να μην περάσουμε το αντικείμενο γιατί δημιουργείται αντίγραφο, αλλά να περάσουμε μία αναφορά σε αυτό, όπως παρακάτω:
 +
 +<code cpp ExceptionUse.cpp>
 +#include <iostream>
 +#include "DerivedException.h"
 +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;
 +}
 +</code>
 +
 +Πλέον το αποτέλεσμα της εκτέλεσης είναι το αναμενόμενο
 +<code>
 +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
 +</code>
 +
 +<WRAP tip 80% center round>
 +Το πιάσιμο μιας εξαίρεσης με χρήση αναφοράς για αντικείμενα σύνθετους τύπου (όχ char, int, long, double κλπ), διότι //α)// αποφεύγουμε την αντιγραφή του αντικειμένου μέσα στο //catch block// (πιο γρήγορος κώδικας) και //β)// αποφεύγουμε την "αποκοπή" μέρους του αντικειμένου που γίνεται //throw// λόγω του "πιασίματος" της εξαίρεσης από ένα //catch block// βασικότερου τύπου από τον τύπο που γίνεται //throw//.
 +</WRAP>
 +
 +<WRAP tip 80% center round>
 +Είναι προφανές ότι η σειρά των //catch blocks// θα έπρεπε να είναι η αντίστροφη (πρώτα το //catch block// για την αναφορά τύπου //DerivedException// και στη συνέχεια το //catch block// για την αναφορά τύπου //BasedException//. Ο λόγος είναι ότι εάν παραμείνει η σειρά των //catch blocks// ως έχει το 2o catch block δεν εκτελείται ποτέ.
 +</WRAP>
 +===== Stack Unwinding =====
 +
 +Κατά την δημιουργία ενός //exception// μέσα σε μία συνάρτηση ή σε ένα κατασκευαστή δεν είναι απαραίτητο ότι η διαχείριση του //exception// θα πρέπει να γίνει στην ίδια τη συνάρτηση ή τον καστασκευαστή. Η διαδικασία όνομάζεται //stack unwinding// και το παράδειγμα που ακολουθεί είναι εξαρειτικά διαφωτιστικό για το πως διαμορφώνεται το stack μετά από την διαχείριση μίας εξαίρεσης σε υψηλότερο επίπεδο.
 +
 +<code cpp StackUnwinding.cpp>
 +#include <iostream>
 +// 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) {
 +     std::cerr << "F caught int exception\n";
 +  } catch (char) {
 +     std::cerr << "F caught double exception\n";
 +  }
 +  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;
 +}
 +</code>
 +
 +Το αποτέλεσμα που εκτυπώνεται στην οθόνη είναι το εξής:
 +<code>
 +Start main
 +Start F
 +Start FF
 +Start FFF
 +Start FFFF
 +FFFF throwing int literal exception
 +F caught int exception
 +End F
 +End main
 +</code>
 +
 +H εξέλιξη του //program stack// στο παρακάτω πρόγραμμα δίνεται στο παρακάτω διάγραμμα. Παρατηρήστε ότι το exception παράγεται στη συνάρτηση **FFFF()**, αλλά "πιάνεται" στην **F()** πράγμα που συνεπάγεται την αυτόματη συρρίκνωση του //stack// στο επίπεδο της συνάρτησης **F()**. Μετά το "πιάσιμο" του //exception//, τα περιεχόμενα του //stack// για τις συναρτήσεις **FFF()** και **FFFF()** έχουν χαθεί.
 +
 +{{ :cpp:stack_unwinding.png |}}
 +
 +===== Κλείσιμο ανοιχτών resources όταν συμβεί ένα exception =====
 +
 +1. Διάβασμα από αρχείο και δέσμευση διδιάστατου πίνακα.
 +
 +===== Δήλωση των εξαιρέσεων που παράγουν οι συναρτήσεις (exception specifiers) =====
 +
 +Κατά τη δήλωση μίας συνάρτησης είναι δυνατόν να ορίσετε εάν αυτή μπορεί να παράγει κάποιου είδους //exception//. Υπάρχουν 3 διαφορετικού τύποι ορισμών οι οποίοι δίνονται παρακάτω:
 +  - ''int functionName() throw()'': Ο συγκεκριμένος ορισμός δηλώνει ότι δεν παράγεται καμία εξαίρεση.
 +  - ''int functionName() throw(std::bad_alloc)'': Ο συγκεκριμένος ορισμός δηλώνει ότι μπορεί να παραχθεί μία εξαίρεση τύπου [[http://en.cppreference.com/w/cpp/memory/new/bad_alloc|std::bad_alloc]].
 +  - ''int functionName() throw(...)'': Ο συγκεκριμένος ορισμός δηλώνει ότι μπορεί να παραχθεί οποιουδήποτε τύπου εξαίρεση.
 +
 +Τους παραπάνω ορισμούς είναι δυνατόν να τους συναντήσετε στις //standard// βιβλιοθήκες της C++. Δεν είναι όμως ευρέως χρησιμοποιούμενοι και η υποστήριξη τους από τους C++ //compilers// είναι συχνά ελλειπής. Δεν συνιστάται η χρήση τους σε κώδικα που γράφετε εσείς, λόγω της ελλειπούς υποστήριξης από την κοινότητα της C++.
 +
 +===== Function try blocks =====
 +
 +===== Η κλάση std::exception =====
 +
 +
 +
 +
 +
 +
 +
  
cpp/exception.txt · Last modified: 2023/05/15 14:01 by gthanos