User Tools

Site Tools


cpp:function_try_blocks

This is an old revision of the document!


Εξαιρέσεις που παράγονται στον κατασκευαστή

Ας υποθέσουμε ότι έχουμε την παρακάτω κλάση Name η οποία περιγράφει ένα όνομα

Name.hpp
#include <cstring>
 
class Name {
  char* name;
public:
  Name();
  Name(const char *name);
  ~Name();
  Name& operator=(const Name&);
};
 
Name::Name() {
  cout << "Name default constructor." << endl;
  name = nullptr;
}
 
Name::Name(const char *name) {
  cout << "Name constructor: " << name << endl;
  this->name = strdup(name);
}
 
Name::~Name() {
  cout << "Name destructor: " << name << endl;
  free(name);
  name = nullptr;
}
 
Name& Name::operator=(const Name& a) {
  if(name != nullptr) 
    free(name);
  name = strdup(a.name);
  return *this;
}

και την κλάση Person η οποία περιγράφει έναν άνθρωπο. Η κλάση διαθέτει τα εξής δύο πεδία:

  Name* firstname;  // pointer 
  Name lastname;

Επιπλέον ο κατασκευαστή της κλάση Person παράγει ένα exception τύπου BadName. Η κλάσεις BadName και Person περιγράφονται παρακάτω:

BadName.hpp
class BadName : public std::exception {
public:
  const char* what() {
    return "BadName";
  }
};
Person.hpp
#include "Name.hpp"
#include "BadName.hpp"
 
class Person {
protected:
  Name* firstname;
  Name  lastname;
public:
  Person(const char *first, const char *last);
  ~Person();
};
 
Person::Person(const char *first, const char *last) {
  cout << "Person constructor" << endl;
  firstname = new Name(first);
  lastname = Name(last);
  BadName bad;
  throw bad;
}
 
Person::~Person() {
  cout << "Person destructor" << endl;
  delete firstname;
}

Εάν μεταγλωττίσουμε και εκτελέσουμε την παρακάτω συνάρτηση main ο κώδικας εκτυπώνει τα εξής:

main.cpp
#include <iostream>
using namespace std;
 
#include "Person.hpp"
 
int main() {
  try {
    Person johnSnow("John", "Snow");
  } catch(BadName& a) {
    cout << "Exception occured: " << a.what() << endl;
  }
}
Name default constructor.
Person constructor
Name constructor: John
Name constructor: Snow
Name destructor: Snow
Name destructor: Snow
Occured: BadName

Από τις εκτυπώσεις, παρατηρούμε ότι καταστρέφεται το αντικείμενο lastname, αλλά όχι και το αντικείμενο firstname. Ο λόγος είναι ότι δημιουργηθεί ένα exception στον κατασκευαστή, δεν καλείται ποτέ ο καταστροφέας της κλάσης. Καταστρέφονται αυτόματα όλα τα αντικείμενα που έχουν δεσμευτεί στο stack, και λαμβάνει χώρα η διαδικασία του stack unwinding που είδαμε σε προηγούμενη παράγραφο.

Ως εκ τούτου, δεν καταστρέφονται τα αντικείμενα που είναι δεσμευμένα στο heap και γενικότερα resources που περιμένουμε να απελευθερωθούν στον καταστροφέα της κλάσης.

Ο λόγος που εκτυπώνεται 2 φορές το μήνυμα “Name destructor: Snow”, είναι διότι δημιουργούνται δύο αντικείμενα τύπου Name, ένα κατά την αρχικοποίηση του αντικειμένου Person και ένα στον κατασκευαστή της κλάσης στη γραμμή lastname = Name(last);.

Function Try Blocks

Προκειμένου να λυθεί το συγκεκριμένο πρόβλημα η C++ υποστηρίζει τα λεγόμενα function try blocks. Ουσιαστικά πρόκειται για μία ειδική σύνταξη try - catch που περιλαμβάνει το σύνολο του κατασκευαστή, ως εξής:

Person::Person(const char *first, const char *last) try {
  cout << "Person constructor" << endl;
  firstname = new Name(first);
  lastname = Name(last);
  BadName bad;
  throw bad;
}
catch(BadName& ex) {
  cout << "--> firstname deleted!" << endl;
  delete firstname;
}

Εάν ανανεώσετε τον κώδικα του κατασκευαστή τώρα το πρόγραμμα σας κατά την εκτέλεση εκτυπώνει:

Name default constructor.
Person constructor
Name constructor: John
Name constructor: Snow
Name destructor: Snow
Name destructor: Snow
--> firstname deleted!
Name destructor: John
Occured: BadName

Παρατηρήστε ότι αν και εκτελείται ο κώδικας μέσα στο catch block, η εξαίρεση “μεταφέρεται” και στη συνάρτηση main/. Ο λόγος είναι ότι η εξαίρεση, αναπαράγεται αυτόματα στο τέλος του catch block. Ο μόνος τρόπος να το αποφύγετε αυτό είναι να κάνετε throw μία νέα εξαίρεση, διαφορετικού τύπου.

Exception που συμβαίνουν στον κατασκευαστή της προγόνου κλάσης

Ας υποθέσουμε την κλάση Student που είναι απόγονος της κλάσης Person και δηλώνεται ως εξής:

Student.hpp
class Student: public Person {
  int aem;
public:
  Student(const char *first, const char *last, int id);
};
 
Student::Student(const char *first, const char *last, int id): Person(first, last), aem(id) {
  cout << "Student constructor" << endl;
} 

Εάν επιθυμούμε να “πιάσουμε” την εξαίρεση εντός του κατασκευαστή της κλάσης Student θα πρέπει να κρατήσουμε τον κατασκευαστή της κλάσης Person στην αρχική του μορφή και να ξαναγράψουμε τον κατασκευαστή:

Student.hpp
class Student: public Person {
  int aem;
public:
  Student(const char *first, const char *last, int id);
};
 
Student::Student(const char *first, const char *last, int id) try : Person(first, last), aem(id) {
  cout << "Student constructor" << endl;
} 
catch(BadName& ex) {
  cout << "--> firstname deleted!" << endl;
  delete firstname;
}

Η παραπάνω σύνταξη επιτρέπει να πιάσουμε το exception που συμβαίνει στον κατασκευαστή της προγόνου κλάσης. Και εδώ το exception θα αναπαραχθεί σύμφωνα με τους κανόνες που ίσχυσαν και στο προηγούμενο παράδειγμα.

cpp/function_try_blocks.1557172085.txt.gz · Last modified: 2019/05/06 18:48 (external edit)