Historia pytania:
C++ był pierwotnie projektowany z naciskiem na wydajność, dlatego zarządzanie zasobami (pamięć, pliki, strumienie, gniazda) często odbywa się ręcznie. W przypadku wystąpienia wyjątku wymagane jest czyszczenie zajętych zasobów. Rozmontowanie stosu — mechanizm używany przez C++ do poprawnego zakończenia pracy funkcji podczas wyrzucania wyjątku.
Problem:
Podczas wyrzucania wyjątku kontrola natychmiast przechodzi do bloków catch, a funkcje pośrednie są "rozmontowane": ich destruktory są wywoływane, ale jawne wywołania funkcji zwalniających mogą zostać pominięte (na przykład, jeśli nie używane są obiekty automatycznie zwalniające zasoby).
Rozwiązanie:
W C++ zwalnianie zasobów należy powierzać destruktorom, a nie wywoływać funkcje zwalniające ręcznie. Wzorzec RAII (Resource Acquisition Is Initialization) to jednoznaczny sposób, aby uczynić zwalnianie zasobów automatycznym. Podczas rozmontowywania stosu destruktor zostanie wywołany, a zasób zostanie zwolniony niezależnie od drogi wyjścia z funkcji.
Przykład kodu:
#include <fstream> #include <stdexcept> void readFile(const std::string& filename) { std::ifstream file(filename); // Zostanie otwarty i prawidłowo zamknięty nawet w przypadku wyjątku if (!file.is_open()) { throw std::runtime_error("Plik nie może być otwarty"); } // ... czytanie pliku } // file zamknie się nawet w przypadku wyjątku
Kluczowe cechy:
Czy delete ptr; w bloku catch jest wystarczające do oczyszczania pamięci?
Nie, jeśli między przydzieleniem pamięci a blokiem catch wystąpi wyjątek, pamięć może nie zostać oczyszczona. Lepiej używać std::unique_ptr lub pisać delete w destruktorze.
Przykład kodu:
void foo() { int* data = new int[10]; // ... throw std::runtime_error("porażka"); delete[] data; // nie zostanie wywołane w przypadku wyjątku }
Czy rozmontowanie stosu może pominąć wywołanie destruktora dla obiektu umieszczonego na stosie?
Nie, wszystkie lokalne obiekty (nie zniszczone przed punktem wyrzucenia wyjątku) będą zniszczone w odwrotnej kolejności tworzenia, destruktory zostaną wywołane gwarantowanie.
Czy można używać goto lub longjmp do wyjścia z bloku try i liczyć na wywołanie destruktorów?
Nie. C++ gwarantuje wywołanie destruktorów tylko przy rozmontowywaniu stosu z powodu wyjątku, a nie z powodu niepoprawnego zarządzania przepływem (goto, setjmp/longjmp).
Programista przydziela pamięć za pomocą new, obsługuje wyjątki, zwalniając pamięć w bloku catch, ale zapomina o innych drogach wyjścia z funkcji.
Zalety:
Wady:
Używane są std::unique_ptr i klasy RAII dla wszystkich zasobów, zwalnianie nie zależy od try/catch.
Zalety:
Wady: