Moje pytanie brzmi zatem: jeśli wyrzucenie z destruktora powoduje niezdefiniowane zachowanie, jak radzisz sobie z błędami występującymi podczas niszczenia?
Główny problem jest następujący: nie można zawieść . Co w końcu znaczy nie zawieść? Jeśli transakcja w bazie danych nie powiedzie się i nie powiedzie się (nie nastąpi przywrócenie), co stanie się z integralnością naszych danych?
Ponieważ destruktory są wywoływane zarówno dla normalnych, jak i wyjątkowych (nieudanych) ścieżek, one same nie mogą zawieść, w przeciwnym razie my „zawiedziemy”.
Jest to problem trudny koncepcyjnie, ale często rozwiązaniem jest po prostu znalezienie sposobu, aby upewnić się, że niepowodzenie nie może zawieść. Na przykład baza danych może zapisać zmiany przed zatwierdzeniem zewnętrznej struktury danych lub pliku. Jeśli transakcja się nie powiedzie, strukturę plików / danych można odrzucić. Wszystko, co musi wtedy zapewnić, to zatwierdzenie zmian z tej zewnętrznej struktury / pliku transakcji atomowej, która nie może zawieść.
Pragmatycznym rozwiązaniem może być po prostu upewnienie się, że szanse niepowodzenia w przypadku awarii są astronomicznie nieprawdopodobne, ponieważ uniemożliwienie porażki może być w niektórych przypadkach prawie niemożliwe.
Najodpowiedniejszym rozwiązaniem dla mnie jest zapisanie logiki nieoczyszczania w taki sposób, aby logika czyszczenia nie mogła zawieść. Na przykład, jeśli masz ochotę utworzyć nową strukturę danych w celu oczyszczenia istniejącej struktury danych, być może możesz spróbować utworzyć tę strukturę pomocniczą z wyprzedzeniem, abyśmy nie musieli już tworzyć jej wewnątrz destruktora.
Trzeba przyznać, że o wiele łatwiej to powiedzieć niż zrobić, ale jest to jedyny naprawdę właściwy sposób, aby to zrobić. Czasami myślę, że powinna istnieć możliwość napisania osobnej logiki destruktora dla normalnych ścieżek wykonania z dala od wyjątkowych, ponieważ czasami destruktory czują się trochę tak, jakby miały podwójną odpowiedzialność, próbując poradzić sobie z obiema (przykładem są osłony zakresów, które wymagają wyraźnego zwolnienia ; nie wymagaliby tego, gdyby mogli odróżnić ścieżki wyjątkowych zniszczeń od ścieżek innych niż wyjątkowe).
Nadal najważniejszym problemem jest to, że nie możemy zawieść, i jest to trudny problem z projektem koncepcyjnym, który idealnie rozwiązuje się we wszystkich przypadkach. Staje się łatwiejsze, jeśli nie zostaniesz zbyt pochłonięty złożonymi strukturami kontrolnymi z mnóstwem małych obiektów oddziałujących ze sobą, a zamiast tego modelujesz swoje projekty w nieco bardziej masywny sposób (przykład: układ cząstek z niszczycielem, aby zniszczyć całą cząsteczkę układ, a nie oddzielny nietrywialny destruktor na cząsteczkę). Kiedy modelujesz swoje projekty na tym grubszym poziomie, masz do czynienia z mniej trywialnymi niszczycielami, z którymi możesz sobie poradzić, i często możesz sobie pozwolić na wszystko, co jest wymagane narzut pamięci / przetwarzania, aby upewnić się, że niszczyciele nie zawiodą.
I to jest jedno z najłatwiejszych rozwiązań, oczywiście, rzadziej używać niszczycieli. W powyższym przykładzie cząstek, być może po zniszczeniu / usunięciu cząstki, należy zrobić pewne rzeczy, które mogą zawieść z jakiegokolwiek powodu. W takim przypadku zamiast wywoływać taką logikę przez dtor cząstki, która mogłaby zostać wykonana na wyjątkowej ścieżce, można zamiast tego zrobić to wszystko przez układ cząstek, gdy usuwa cząstkę. Usunięcie cząstki zawsze może odbywać się na nietypowej ścieżce. Jeśli system zostanie zniszczony, być może może po prostu oczyścić wszystkie cząstki i nie zawracać sobie głowy logiką usuwania pojedynczych cząstek, która może zawieść, podczas gdy logika, która może zawieść, jest wykonywana tylko podczas normalnego działania układu cząstek, gdy usuwa jedną lub więcej cząstek.
Często istnieją takie rozwiązania, które pojawiają się, jeśli unikniesz zajmowania się wieloma małymi obiektami z nietrywialnymi niszczycielami. Kiedy możesz zaplątać się w bałagan, w którym wydaje się prawie niemożliwe, aby być wyjątkowym, bezpieczeństwo jest wtedy, gdy zaplątasz się w wiele małych obiektów, z których wszystkie mają nietrywialne cechy.
Bardzo by to pomogło, gdyby nothrow / noexcept faktycznie zostało przetłumaczone na błąd kompilatora, jeśli cokolwiek, co go określa (w tym funkcje wirtualne, które powinny odziedziczyć specyfikację noexcept klasy bazowej), próbowałoby wywołać wszystko, co mogłoby wyrzucić. W ten sposób moglibyśmy uchwycić wszystkie te rzeczy w czasie kompilacji, gdybyśmy faktycznie przypadkowo napisali destruktor, który mógłby rzucić.