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 obiektuPo 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