Jak najwyraźniej już przypuszczałeś, tak, C ++ zapewnia te same możliwości bez tego mechanizmu. Jako taki, ściśle mówiąc, mechanizm try
/ finally
nie jest tak naprawdę konieczny.
To powiedziawszy, bez niego nakłada się pewne wymagania dotyczące sposobu zaprojektowania reszty języka. W C ++ ten sam zestaw akcji jest wbudowany w destruktor klasy. Działa to przede wszystkim (wyłącznie?), Ponieważ wywołanie destruktora w C ++ jest deterministyczne. To z kolei prowadzi do dość skomplikowanych zasad dotyczących czasu życia obiektów, z których niektóre są zdecydowanie nieintuicyjne.
Większość innych języków zapewnia zamiast tego jakąś formę śmiecia. Chociaż istnieją pewne kwestie związane z odśmiecaniem pamięci, które są kontrowersyjne (np. Jego wydajność w porównaniu z innymi metodami zarządzania pamięcią), jedna rzecz na ogół nie jest: dokładny czas, kiedy obiekt zostanie „wyczyszczony” przez moduł odśmiecający, nie jest bezpośrednio związany do zakresu obiektu. Zapobiega to jego wykorzystaniu, gdy czyszczenie musi być deterministyczne, gdy jest po prostu wymagane do prawidłowego działania lub w przypadku zasobów tak cennych, że ich czyszczenie nie może być opóźnione w sposób arbitralny. try
/ finally
zapewnia sposób, w jaki takie języki radzą sobie z sytuacjami, które wymagają tego deterministycznego czyszczenia.
Myślę, że ci, którzy twierdzą, że składnia C ++ dla tej możliwości jest „mniej przyjazna” niż Java, raczej nie rozumieją tego. Co gorsza, brakuje im znacznie ważniejszego punktu na temat podziału odpowiedzialności, który wykracza daleko poza składnię i ma znacznie więcej wspólnego ze sposobem projektowania kodu.
W C ++ to deterministyczne czyszczenie odbywa się w destruktorze obiektu. Oznacza to, że obiekt można (i normalnie powinien być) zaprojektowany do czyszczenia po sobie. Odnosi się to do istoty projektowania obiektowego - klasa powinna być zaprojektowana w celu zapewnienia abstrakcji i egzekwowania własnych niezmienników. W C ++ robi się dokładnie to - a jednym z niezmienników, dla których zapewnia, jest to, że gdy obiekt zostanie zniszczony, zasoby kontrolowane przez ten obiekt (wszystkie, nie tylko pamięć) zostaną poprawnie zniszczone.
Java (i podobne) są nieco inne. Chociaż obsługują (w pewnym sensie) obsługę, finalize
która teoretycznie może zapewnić podobne możliwości, obsługa jest tak słaba, że w zasadzie jest bezużyteczna (i w zasadzie nigdy nie jest używana).
W rezultacie, zamiast sama klasa mogła wykonać wymagane czyszczenie, klient klasy musi podjąć odpowiednie kroki. Jeśli zrobimy wystarczająco krótkowzroczne porównanie, na pierwszy rzut oka może się wydawać, że różnica ta jest dość niewielka, a Java pod tym względem dość konkurencyjna względem C ++. W efekcie powstaje coś takiego. W C ++ klasa wygląda mniej więcej tak:
class Foo {
// ...
public:
void do_whatever() { if (xyz) throw something; }
~Foo() { /* handle cleanup */ }
};
... a kod klienta wygląda mniej więcej tak:
void f() {
Foo f;
f.do_whatever();
// possibly more code that might throw here
}
W Javie wymieniamy trochę więcej kodu, w którym obiekt jest używany nieco mniej w klasie. Ten początkowo wygląda całkiem nawet kompromisu. W rzeczywistości, to jest daleko od tego jednak, bo w większości typowych kodu tylko definiujemy klasę w jednym miejscu, ale używać go w wielu miejscach. Podejście C ++ oznacza, że ten kod piszemy tylko w jednym miejscu. Podejście Java oznacza, że musimy napisać ten kod, aby obsłużyć czyszczenie wiele razy, w wielu miejscach - w każdym miejscu, w którym używamy obiektu tej klasy.
Krótko mówiąc, podejście Java zasadniczo gwarantuje, że wiele abstrakcji, które staramy się zapewnić, jest „nieszczelnych” - każda klasa, która wymaga deterministycznego czyszczenia zobowiązuje klienta tej klasy do uzyskania szczegółowych informacji na temat tego, co należy wyczyścić i jak to zrobić , a nie te szczegóły są ukryte w samej klasie.
Chociaż nazwałem to „podejściem Java” powyżej i try
/ finally
lub podobne mechanizmy pod innymi nazwami nie są całkowicie ograniczone do Javy. W jednym widocznym przykładzie większość (wszystkie?) Języków .NET (np. C #) zapewnia to samo.
Ostatnie iteracje zarówno Java, jak i C # również pod tym względem stanowią coś w połowie drogi między „klasyczną” Javą a C ++. W języku C # obiekt, który chce zautomatyzować czyszczenie, może zaimplementować IDisposable
interfejs, który zapewnia Dispose
metodę (przynajmniej niejasną) podobną do destruktora C ++. Podczas gdy można tego użyć za pomocą try
/ finally
jak w Javie, C # nieco automatyzuje zadanie za pomocą using
instrukcji, która pozwala zdefiniować zasoby, które zostaną utworzone po wprowadzeniu zakresu i zniszczone po wyjściu z zakresu. Mimo że wciąż brakuje mu poziomu automatyzacji i pewności zapewnionego przez C ++, jest to znacząca poprawa w stosunku do Javy. W szczególności projektant klasy może scentralizować szczegóły tego, jak to zrobićpozbyć się klasy w jej implementacji IDisposable
. Dla programisty klienckiego pozostaje tylko mniejszy ciężar napisania using
instrukcji, aby mieć pewność, że IDisposable
interfejs zostanie użyty w odpowiednim czasie. W Javie 7 i nowszych nazwy zostały zmienione, aby chronić winnych, ale podstawowa idea jest w zasadzie identyczna.