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 [2019/05/06 07:17] – [Κληρονομικότητα εξαιρέσεων] gthanos
Line 1: Line 1:
-====== Διαχείριση Εξαιρέσεων ======+====== Δημιουργία & Διαχείριση Εξαιρέσεων ======
  
-Ας εξετάσουμε την κλάση **Vector** που είδαμε στην υπερφόρτωση των τελεστών. Ο προσδιοριστής //nothrow// σε συνδυασμό με τον τελεστή **new** μας υποχρεώνει να ελέγξουμε την επιστρεφόμενη τιμή του τελεστή **new** για να δούμε έαν έχει αποτύχει η διαδικασία δέσμευσης μνήμης ή όχι και στην περίπτωση που έχουμε αποτυχία τερματίζουμε το πρόγραμμα. +Ας εξετάσουμε την κλάση **Vector** που είδαμε στην ενότητα της υπερφόρτωση τελεστών. Ο προσδιοριστής //nothrow// σε συνδυασμό με τον τελεστή **new** μας υποχρεώνει να ελέγξουμε την επιστρεφόμενη τιμή του τελεστή **new** για να δούμε έαν έχει αποτύχει η διαδικασία δέσμευσης μνήμης ή όχι και στην περίπτωση που έχουμε αποτυχία τερματίζουμε το πρόγραμμα. 
  
 <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 38: Line 43:
 </code> </code>
  
-Αν και η παραπάνω διαδικασία δεν είναι λανθασμένη, έχει το βασικό μειονέκτημα ότι θα πρέπει να τερματίσουμε το πρόγραμμα, ακόμη και εάν ο λόγος αποτυχίας είναι ότι ο χρήστης της κλάσης επέτρεψε το πέρασμα αρνητικής τιμής ως όρισμα στον κατασκευαστή.+Αν και η παραπάνω διαδικασία δεν είναι λανθασμένη, έχει το βασικό μειονέκτημα ότι θα πρέπει να τερματίσουμε το πρόγραμμα, ακόμη και εάν ο λόγος αποτυχίας είναι ότι ο χρήστης της κλάσης επέτρεψε το πέρασμα αρνητικής τιμής ως όρισμα στον κατασκευαστή. Ο λόγος είναι ότι στην περίπτωση που αποτύχει η δέσμευση της μνήμης, λόγω λανθασμένου ορίσματος, ο κατασκευαστής της κλάσης **Vector** επιστρέφει ένα αντικείμενο το οποίο δεν είναι σωστά αρχικοποιημένο.
  
 <code cpp VectorUse.cpp> <code cpp VectorUse.cpp>
 +#include "Vector.cpp"
 +
 int main() { int main() {
   int size;   int size;
Line 51: Line 58:
 </code> </code>
  
-Η παραγωγή ενός exception μπορεί να επιλύσει πιο αποτελεσματικά το παραπάνω πρόβλημα διότι επιτρέπει την διαχείριση γεγονότων που δεν επιτρέπουν την ομαλή ροή του προγράμματος. Στο παράδειγμα του κατασκευαστή της κλάσης **Vector**, η αποτυχία κλήσης του τελεστή **new** (χωρίς τον προσδιοριστή //notrhow//) παράγει ένα //exception// τύπου [[http://www.cplusplus.com/reference/new/bad_alloc/|std::bad_alloc]], το οποίο μπορούμε να διαχειριστούμε, όπως παρακάτω:+Η παραγωγή ενός //exception// μπορεί να επιλύσει πιο αποτελεσματικά το παραπάνω πρόβλημαδιότι υποστηρίζει τη διαχείριση συμβάντων που δεν επιτρέπουν την ομαλή εκτέλεση του προγράμματος. Στο παράδειγμα του κατασκευαστή της κλάσης **Vector**, η αποτυχία κλήσης του τελεστή **new** (χωρίς τον προσδιοριστή //notrhow//) παράγει ένα //exception// τύπου [[http://www.cplusplus.com/reference/new/bad_alloc/|std::bad_alloc]], το οποίο μπορούμε να διαχειριστούμε στη μέθοδο //main//, όπως παρακάτω:
  
 <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 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 &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// πριν καλέσει τον κατασκευαστή. Σε αυτή την περίπτωση, ο έλεγχος θα πρέπει να γίνεται από το χρήστη της εκάστοτε βιβλιοθήκης, ενώ η βιβλιοθήκη δεν παρέχει καμία εγγύηση για τον τρόπο συμπεριφοράς της εάν περαστούν λανθασμένα ορίσματα. Τα //exceptions// εξασφαλίζουν ότι η βιβλιοθήκη θα παράξει μία εξαίρεση εάν η δέσμευση της μνήμης αποτύχει.
 +</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 std::string
 +        break;
 +      case 5:
 +        throw ex; //throw a MyException object
 +        break;
 +      default:
 +        c = -10; throw c;  // throw a character
 +        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>
 +
 +
 +===== 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 |}}
 +
 +===== Διαχείριση μίας εξαίρεσης και παραγωγή νέας εξαίρεσης κατά την διαχείριση της =====
 +
 +Κάποιες φορές είναι επιθυμητό να διαχειριστούμε μία εξαίρεση προκειμένου να κλείσουμε κάποιο //resource//, αλλά στη συνέχεια θέλουμε να παράγουμε ξανά την ίδια εξαίρεση προκειμένου η τελική διαχείριση να γίνει παρακάτω. Δείτε το επόμενο απόσπασμα κώδικα από την κλάση PPMImage. Εάν το αρχείο που διαβάζουμε περιέχει κατά λάθος μία αρνητική τιμή θα παραχθεί ένα //std::bad_alloc exception//. Δεν θέλουμε να το διαχειριστούμε μέσα στον κατασκευαστή, διότι σε αυτή την περίπτωση ο κατασκευαστής θα επιστρέψει κανονικά και ο χρήστης δεν θα έιναι σε θέση να γνωρίζει ότι συνέβη σφάλμα. Παρόλα αυτά, θα θέλαμε να διαχειριστούμε εν μέρη την εξαίρεση στον κατασκευαστή, ώστε να κλείσουμε το ανοιχτό //ifstream//, αλλά στη συνέχεια να παράγουμε την ίδια εξαίρεση την οποία θα κληθεί η διαχειριστεί η μέθοδος που δημιουργεί το αντικείμενο.
 +
 +<code cpp PPMImageSample.cpp>
 +#include <iostream>
 +#include <fstream>
 +#include <ios>
 +#include <cstdlib>
 +
 +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::ios_base::failure fex("File not found!");
 +      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<height; row++)
 +        raster[row] = new int[width];
 +      for(int row=0; row<height; row++) {
 +        for(int col=0; col<width; 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::bad_alloc &ex) {
 +      cerr << "std::bad_alloc occured!\n";
 +      in.close();
 +      throw ex;
 +    }
 +  }
 +  
 +  ~PPMImage() {
 +    for(int row=0; row<height; 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::failure &fex) {
 +    cerr << "File '" << argv[0] << "' was not found!\n";
 +  }
 +  catch(bad_alloc &ex) {
 +    cerr << "Memory allocation failure!\n";
 +    if (imgptr!=nullptr) {
 +      cerr << "imgptr != nullptr\n";
 +      if(imgptr->getRaster() != nullptr) {
 +        cerr << "imgptr->getRaster() != nullptr\n";
 +        delete imgptr->getRaster();
 +      }
 +      else {
 +        cerr << "imgptr->getRaster() == nullptr\n";
 +      }
 +      delete imgptr;
 +    }
 +    else {
 +      cerr << "imgptr == nullptr\n";
 +    }
 +  }
 +  delete imgptr;
 +  return 0;
 +}
 +</code>
 +
 +Το ενδεικτικό αρχείο εισόδου είναι το παρακάτω:
 +
 +<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
 +</code>
 +
 +Από τον παραπάνω κώδικα μπορούμε να συμπεράνουμε τα εξής:
 +  - Εφόσον παράγεται ένα //exception// o δείκτης //imgptr// μέσα στο //catch block// της συνάρτησης //main// έχει την αρχική του τιμή, δηλαδή **nullptr**. Αυτό είναι λογικό με βάση τις αρχές του //stack unwinding// που συζητήσαμε προηγούμενα.
 +  - Εάν εφαρμόσω τον τελεστή **delete** σε ένα δείκτη που έχει την τιμή **nullptr**, δεν παράγεται κάποιου είδους //exception//, αλλά ο κώδικας συνεχίζει κανονικά.
 +
 +==== Ένα 2ο παράδειγμα ====
 +
 +Στο προηγούμενο παράδειγμα κάντε την εξής αλλαγή. Αντικαταστήστε το //catch block// στον κατασκευαστή με το παρακάτω:
 +
 +<code cpp>
 +    catch(std::exception &ex) {
 +      cerr << "std::exception occured!\n";
 +      in.close();
 +      throw ex;
 +    }
 +</code>
 +
 +Το //exception std::bad_alloc// είναι απόγονος της κλάσης //std::exception// επομένως ο παραπάνω κώδικας θα πρέπει να δουλεύει σωστά και μετά την αλλαγή. Εν τούτοις παρατηρούμε ότι το πρόγραμμα αποτυγχάνει με ένα μήνυμα της μορφής
 +<code>
 +$> ./PPMImageSample 3x2.ppm 
 +std::bad_alloc occured!
 +terminate called after throwing an instance of 'std::exception'
 +  what():  std::exception
 +Aborted (core dumped)
 +</code>
 +
 +Ο λόγος που συμβαίνει το παραπάνω είναι ότι το όταν το //catch block// παράγει και πάλι το //exception//, παράγει ένα αντικείμενο της κλάσης //std::exception// αποκόπτοντας το τμήμα του αντικειμένου που αφορά την απόγονο κλάση //std::bad_alloc//. Στη συνέχεια ένα τέτοιο αντικείμενο δεν μπορεί να το "πιάσει" το //catch block// της συνάρτησης //main//. Εάν πρέπει να παράγεται το ίδιο //exception// πράγμα που ήταν αρχικά επιθυμητό ο κώδικας στο //catch block// του κατασκευαστή θα πρέπει να γραφεί όπως παρακάτω, ώστε να παράγει ως //exception// το ίδιο αντικείμενο που έγινε //catch//.
 +
 +<code cpp>
 +    catch(std::exception &ex) {
 +      cerr << "std::exception occured!\n";
 +      in.close();
 +      throw;       // rethrows the same exception object
 +    }
 +</code>
 +===== Δήλωση των εξαιρέσεων που παράγουν οι συναρτήσεις (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 =====
 +*/
 +
 +
 +
 +
 +
 +
 +
  
cpp/exception.txt · Last modified: 2023/05/15 14:01 by gthanos