Sytuacja wyjątkowa może zdarzyć się podczas wykonywania konstruktora lub destruktora. Taka sytuacja jest szczególnie trudna do właściwej obsługi. Powiedzmy zatem o kilku sprawach, o których trzeba wtedy pamiętać.
Jeśli wyjątek został zgłoszony podczas konstrukcji obiektu, to
obiekt ten nie powstanie, jego destruktor nie zostanie wywołany, a
wszystkie do tej pory utworzone składowe zostaną usunięte. Mogą
to być już utworzone składowe obiektowe: dla nich destruktory
zostaną wywołane. Oczywiście powstanie kłopot, jeśli są
w klasie składowe wskaźnikowe, a same obiekty, na które one
wskazują, zostały w konstruktorze zaalokowane na stercie lub odnoszą
się do zasobów systemowych, jak np. plików. Tego typu obiekty są
zwykle usuwane (zwalniane) w destruktorze, ale on nie zadziała. W ten
sposób, w razie wystąpienia sytuacji wyjątkowej, nieudany obiekt
zostanie co prawda usunięty, ale zasoby (pamięć, otwarte pliki) nie
zostaną zwolnione. Można temu zaradzić „opakowując” tego rodzaju
składowe wskaźnikowe tak, aby uczynić z nich obiekty, dla których
w razie niepowodzenia wywołany zostanie destruktor zwalniający zasoby.
Rozpatrzmy przykład:
1. #include <iostream>
2. #include <cstring>
3. #include <cstdio> // FILE, fopen, fclose
4. using namespace std;
5.
6. class A {
7. struct nazw {
8. char* n;
9. nazw(const char* n)
10. : n(strcpy(new char[strlen(n)+1],n))
11. { }
12. ~nazw() {
13. cerr << "dtor nazw: " << n << endl;
14. delete [] n;
15. }
16. };
17.
18. nazw Nazwisko;
19. FILE* plik;
20. public:
21. A(const char* n, const char* p)
22. : Nazwisko(n)
23. {
24. plik = fopen(p,"r");
25. // ...
26. // throw 1;
27. // ...
28. }
29.
30. // inne pola i metody
31.
32. ~A() {
33. cerr << "dtor A" << endl;
34. if (plik) fclose(plik);
35. }
36. };
37.
38. int main() {
39. try {
40. A a("Kowalski","zasob.cpp");
41. } catch(...) {
42. cerr << "Nie udalo sie skonstruowac obiektu\n";
43. }
44. }
Prócz nazwiska, klasa A zawiera pole wskaźnikowe wskazujące obiekt typu FILE (jest to standardowy typ w czystym C opisujący pliki).
Załóżmy, że linia 26 (throw 1) jest wykomentowana. Konstruktor klasy A inicjuje składowe opisujące nazwisko i kończy się prawidłowo. Żaden wyjątek nie został zgłoszony. Po wyjściu sterowania z ciała bloku try obiekt klasy A, jako obiekt lokalny dla tego bloku, jest usuwany i wywoływany jest jego destruktor zamykający plik. Następnie usuwane są obiekty składowe i wywoływane są ich destruktory, a więc w naszym przypadku usunięty będzie obiekt Nazwisko, a w jego destruktorze zwolniona zostanie pamięć na nazwisko. Wydruk programu
dtor A
dtor nazw: Kowalski
świadczy o tym, że obiekt
a
został prawidłowo
usunięty.
Spróbujmy teraz uaktywnić linię 26, która powoduje powstanie sytuacji wyjątkowej w trakcie wykonywania konstruktora. Teraz wydruk z programu to
dtor nazw: Kowalski
Nie udalo sie skonstruowac obiektu
Po powstaniu wyjątku destruktor klasy
A
dla powstającego
obiektu nie został wywołany. Tak więc plik, choć już otwarty,
nie został zamknięty — przepadł tylko wskaźnik do niego.
Natomiast napis zawierający nazwisko został prawidłowo usunięty!
Stało się tak, bo powstanie wyjątku spowodowało wywołanie
destruktorów dla już utworzonych składowych obiektowych, a więc
dla składowej
Nazwisko.
W przykładzie powyższym nie wyłapywaliśmy wyjątku powstającego podczas konstruowania obiektu w samym konstruktorze, ale pozwoliliśmy mu wyjść poza konstruktor, gdzie był przechwytywany w funkcji main. Inna jest sytuacja z wyjątkami, jakie mogą powstać w trakcie wykonania destruktora. Problem polega na tym, że destruktor, jak mówiliśmy, może zostać wywołany podczas zwijania stosu w poszukiwaniu procedury obsługi innego wyjątku. Powstanie dodatkowego nieobsłużonego wyjątku w destruktorze powodowałoby „podwójne” zwijanie stosu. Taka sytuacja nie jest w C++ możliwa; jeśli powstanie, program jest natychmiast kończony za pomocą funkcji terminate. Tak więc, jeśli jakikolwiek wyjątek może być zgłoszony podczas wykonywania destruktora, to należy go obsłużyć — przechwycić odpowiednią frazą catch — wewnątrz tego destruktora, nie dopuszczając do jego „ucieczki”.
T.R. Werner, 21 lutego 2016; 20:17