Czy C ++ obsługuje bloki „w końcu ”?
Co to jest idiom RAII ?
Jaka jest różnica między idiomem RAII C ++ a instrukcją „using” w języku C # ?
Czy C ++ obsługuje bloki „w końcu ”?
Co to jest idiom RAII ?
Jaka jest różnica między idiomem RAII C ++ a instrukcją „using” w języku C # ?
Odpowiedzi:
Nie, C ++ nie obsługuje bloków „nareszcie”. Powodem jest to, że C ++ zamiast tego obsługuje RAII: „Resource Acquisition Is Initialization” - kiepska nazwa † dla naprawdę przydatnej koncepcji.
Chodzi o to, że destruktor obiektu jest odpowiedzialny za uwolnienie zasobów. Gdy obiekt ma automatyczny czas przechowywania, niszczyciel obiektu zostanie wywołany, gdy blok, w którym został utworzony, zostanie zamknięty - nawet gdy ten blok zostanie opuszczony w obecności wyjątku. Oto wyjaśnienie tego tematu przez Bjarne Stroustrup .
Częstym zastosowaniem RAII jest blokowanie muteksu:
RAII upraszcza także używanie obiektów jako członków innych klas. Gdy klasa będąca właścicielem zostanie zniszczona, zasób zarządzany przez klasę RAII zostaje zwolniony, ponieważ w wyniku tego wywoływany jest destruktor dla klasy zarządzanej przez RAII. Oznacza to, że gdy używasz RAII dla wszystkich członków w klasie zarządzającej zasobami, możesz uniknąć bardzo prostego, może nawet domyślnego, destruktora dla klasy właściciela, ponieważ nie musi ręcznie zarządzać czasem życia zasobów członkowskich . (Podziękowania dla Mike'a B za zwrócenie na to uwagi.)
Dla tych, którzy znają C # lub VB.NET, możesz rozpoznać, że RAII jest podobny do deterministycznego niszczenia platformy .NET za pomocą instrukcji IDisposable i „using” . Rzeczywiście obie metody są bardzo podobne. Główną różnicą jest to, że RAII deterministycznie uwolni dowolny rodzaj zasobu - w tym pamięć. Podczas implementacji IDisposable w .NET (nawet w języku .NET C ++ / CLI) zasoby zostaną deterministycznie zwolnione, z wyjątkiem pamięci. W .NET pamięć nie jest zwalniana deterministycznie; pamięć jest zwalniana tylko podczas cykli odśmiecania.
† Niektórzy ludzie uważają, że „Zniszczenie to rezygnacja z zasobów” jest dokładniejszą nazwą idiomu RAII.
źródło
W C ++ ostatecznie NIE jest wymagane z powodu RAII.
RAII przenosi odpowiedzialność za bezpieczeństwo wyjątku od użytkownika obiektu na projektanta (i implementatora) obiektu. Twierdzę, że jest to właściwe miejsce, ponieważ wystarczy raz uzyskać poprawność bezpieczeństwa wyjątku tylko raz (w projekcie / realizacji). Używając wreszcie, musisz uzyskać poprawne bezpieczeństwo wyjątku za każdym razem, gdy używasz obiektu.
Również IMO kod wygląda ładniej (patrz poniżej).
Przykład:
Obiekt bazy danych. Aby upewnić się, że używane jest połączenie DB, należy je otworzyć i zamknąć. Za pomocą RAII można to zrobić w konstruktorze / destruktorze.
C ++ Jak RAII
Zastosowanie RAII sprawia, że prawidłowe użycie obiektu DB jest bardzo łatwe. Obiekt DB poprawnie zamknie się za pomocą destruktora, bez względu na to, jak spróbujemy go nadużyć.
Java jak w końcu
Przy ostatecznym użyciu poprawne użycie obiektu zostaje przekazane użytkownikowi obiektu. tzn. użytkownik obiektu jest odpowiedzialny za prawidłowe jawne zamknięcie połączenia DB. Teraz możesz argumentować, że można to zrobić w finalizatorze, ale zasoby mogą mieć ograniczoną dostępność lub inne ograniczenia, a zatem ogólnie chcesz kontrolować uwalnianie obiektu, a nie polegać na niedeterministycznym zachowaniu modułu wyrzucającego śmieci.
To także prosty przykład.
Jeśli masz wiele zasobów, które należy zwolnić, kod może się skomplikować.
Bardziej szczegółową analizę można znaleźć tutaj: http://accu.org/index.php/journals/236
źródło
// Make sure not to throw exception if one is already propagating.
Ważne jest, aby destruktory C ++ również nie zgłaszały wyjątków z tego właśnie powodu.RAII jest zwykle lepsze, ale można łatwo się wreszcie semantykę w C ++. Za pomocą niewielkiej ilości kodu.
Poza tym podstawowe wytyczne C ++ dają wreszcie.
Oto link do implementacji GSL Microsoft i link do implementacji Martin Moene
Bjarne Stroustrup wiele razy powiedział, że wszystko, co jest w GSL, w końcu oznaczało pójście w standard. Powinien to być w końcu przyszłościowy sposób użycia .
Możesz jednak łatwo wdrożyć się, jeśli chcesz, kontynuuj czytanie.
W C ++ 11 RAII i lambdas pozwala w końcu na generała:
przykład zastosowania:
wyjście będzie:
Osobiście użyłem tego kilka razy, aby zapewnić zamknięcie deskryptora pliku POSIX w programie C ++.
Posiadanie prawdziwej klasy, która zarządza zasobami i dzięki temu unika wszelkiego rodzaju wycieków, jest zwykle lepsze, ale w końcu jest to przydatne w przypadkach, w których tworzenie klasy brzmi jak przesada.
Poza tym, jak to lepiej niż inne języki wreszcie dlatego, jeżeli są stosowane w sposób naturalny piszesz kod zamykający pobliżu kodu otwierającego (w moim przykładzie ten nowy i kasowania ) i następuje zniszczenie konstrukcji w celu LIFO, jak zwykle w C ++. Jedynym minusem jest to, że dostajesz zmienną automatyczną, której tak naprawdę nie używasz, a składnia lambda sprawia, że jest trochę głośna (w moim przykładzie w czwartym wierszu znaczenie ma tylko słowo w końcu i blok {} po prawej stronie mają znaczenie reszta to zasadniczo hałas).
Inny przykład:
Element wyłączający jest przydatny, jeśli w końcu należy wywołać tylko w przypadku awarii. Na przykład, musisz skopiować obiekt do trzech różnych pojemników, możesz w końcu skonfigurować, aby cofać każdą kopię i wyłączać po pomyślnym wykonaniu wszystkich kopii. Robiąc to, jeśli zniszczenie nie będzie możliwe, zapewnisz silną gwarancję.
wyłącz przykład:
Jeśli nie możesz użyć C ++ 11, możesz w końcu go mieć , ale kod staje się nieco bardziej skomplikowany. Wystarczy zdefiniować strukturę za pomocą tylko konstruktora i destruktora, konstruktor odwołuje się do wszystkiego, co jest potrzebne, a destruktor wykonuje niezbędne czynności. Jest to w zasadzie to, co robi lambda, wykonywane ręcznie.
źródło
FinalAction
jest zasadniczo taki sam jak popularnyScopeGuard
idiom, tylko z inną nazwą.Oprócz ułatwienia czyszczenia obiektów opartych na stosie, RAII jest również użyteczny, ponieważ to samo „automatyczne” czyszczenie występuje, gdy obiekt należy do innej klasy. Gdy klasa będąca właścicielem zostanie zniszczona, zasób zarządzany przez klasę RAII zostaje wyczyszczony, ponieważ w wyniku tego wywoływany jest dtor dla tej klasy.
Oznacza to, że po osiągnięciu nirwany RAII i wszyscy członkowie klasy używają RAII (jak inteligentne wskaźniki), możesz uzyskać bardzo prosty (może nawet domyślny) dtor dla klasy właściciela, ponieważ nie musi ręcznie zarządzać jej okresy istnienia zasobów członkowskich.
źródło
Właściwie, języki oparte na Garbage collectorach potrzebują „wreszcie” więcej. Śmieciarka nie niszczy obiektów w odpowiednim czasie, więc nie można polegać na prawidłowym usuwaniu problemów niezwiązanych z pamięcią.
Jeśli chodzi o dane przydzielane dynamicznie, wielu twierdzi, że powinieneś używać inteligentnych wskaźników.
Jednak...
Niestety jest to jego własny upadek. Stare nawyki programowania C umierają ciężko. Gdy korzystasz z biblioteki napisanej w C lub bardzo w stylu C, RAII nie będzie używane. Bez przepisywania całego interfejsu API, właśnie z tym musisz pracować. Wtedy brak „w końcu” naprawdę gryzie.
źródło
CleanupFailedException
. Czy istnieje jakiś możliwy sposób na osiągnięcie takiego wyniku przy użyciu RAII?SomeObject.DoSomething()
metodę i chce wiedzieć, czy (1) się udało, (2) nie powiodło się bez efektów ubocznych , (3) nie powiodło się ze skutkami ubocznymi, z którymi program wywołujący jest przygotowany lub (4) nie powiodło się z efektami ubocznymi, z którymi osoba dzwoniąca nie może sobie poradzić. Tylko dzwoniący będzie wiedział, z jakimi sytuacjami może sobie poradzić; to, czego potrzebuje dzwoniący, to sposób na poznanie sytuacji. Szkoda, że nie ma standardowego mechanizmu dostarczania najważniejszych informacji o wyjątku.Kolejna emulacja bloku „w końcu” za pomocą funkcji lambda C ++ 11
Miejmy nadzieję, że kompilator zoptymalizuje powyższy kod.
Teraz możemy napisać taki kod:
Jeśli chcesz, możesz zawinąć ten idiom w makra „spróbuj - w końcu”:
Teraz blok „w końcu” jest dostępny w C ++ 11:
Osobiście nie podoba mi się wersja „w końcu” „makro” i wolałbym używać czystej funkcji „with_finally”, nawet jeśli składnia jest w tym przypadku bardziej nieporęczna.
Możesz przetestować powyższy kod tutaj: http://coliru.stacked-crooked.com/a/1d88f64cb27b3813
PS
Jeśli potrzebujesz wreszcie zablokować w kodzie, a następnie scoped strażników lub ON_FINALLY / ON_EXCEPTION makra będzie prawdopodobnie lepiej dopasowane do Twoich potrzeb.
Oto krótki przykład użycia ON_FINALLY / ON_EXCEPTION:
źródło
Przepraszamy za wykopanie tak starego wątku, ale w następującym uzasadnieniu występuje poważny błąd:
Najczęściej musisz radzić sobie z dynamicznie przydzielanymi obiektami, dynamicznymi liczbami obiektów itp. W ramach try-block, niektóre kody mogą tworzyć wiele obiektów (ile jest określanych w czasie wykonywania) i przechowywać do nich wskaźniki na liście. To nie jest egzotyczny scenariusz, ale bardzo powszechny. W takim przypadku chciałbyś napisać coś takiego
Oczywiście sama lista zostanie zniszczona, gdy wyjdzie poza zakres, ale to nie wyczyści tymczasowych obiektów, które utworzyłeś.
Zamiast tego musisz wybrać brzydką drogę:
Ponadto: dlaczego nawet zarządzane sieci zapewniają ostateczny blok, mimo że zasoby są i tak automatycznie zwalniane przez śmieciarza?
Wskazówka: „w końcu” możesz zrobić więcej niż tylko zwolnienie pamięci.
źródło
new
nie zwraca NULL, zamiast tego zgłasza wyjątekstd::shared_ptr
istd::unique_ptr
bezpośrednio w stdlib.FWIW, Microsoft Visual C ++ obsługuje wreszcie próbę, i w przeszłości był stosowany w aplikacjach MFC jako metoda wychwytywania poważnych wyjątków, które w przeciwnym razie spowodowałyby awarię. Na przykład;
W przeszłości korzystałem z tego, aby robić kopie zapasowe otwartych plików przed wyjściem. Niektóre ustawienia debugowania JIT spowodują jednak uszkodzenie tego mechanizmu.
źródło
Jak wskazano w innych odpowiedziach, C ++ może obsługiwać
finally
podobną funkcjonalność. Wdrożenie tej funkcjonalności, która prawdopodobnie jest najbliższa byciu częścią standardowego języka, jest tym towarzyszącym Podstawowym Wytycznym C ++ , zestawowi najlepszych praktyk korzystania z C ++ edytowanym przez Bjarne Stoustrup i Herb Sutter. Realizacjafinally
jest częścią Wytycznych Wsparcia Library (GSL). W Wytycznych zaleca się korzystanie zfinally
interfejsów w starym stylu, a także ma własną wytyczną zatytułowaną Użyj obiektu aktywności końcowej do wyrażenia czyszczenia, jeśli nie jest dostępny odpowiedni uchwyt zasobów .C ++ nie tylko obsługuje C ++
finally
, ale zaleca się używanie go w wielu typowych przypadkach.Przykład użycia implementacji GSL wyglądałby następująco:
Implementacja i użycie GSL jest bardzo podobne do tego w odpowiedzi Paolo.Bolzoni . Jedną różnicą jest to, że obiekt utworzony przez
gsl::finally()
brakdisable()
połączenia. Jeśli potrzebujesz tej funkcji (powiedzmy, aby zwrócić zasób po jego zmontowaniu i nie wystąpią żadne wyjątki), możesz preferować implementację Paolo. W przeciwnym razie korzystanie z GSL jest tak zbliżone do korzystania ze standardowych funkcji, jak to tylko możliwe.źródło
Nie bardzo, ale możesz je naśladować do pewnego stopnia, na przykład:
Zauważ, że blok w końcu może sam zgłosić wyjątek, zanim oryginalny wyjątek zostanie ponownie zgłoszony, odrzucając w ten sposób oryginalny wyjątek. Jest to dokładnie to samo zachowanie, co w przypadku ostatecznego bloku Java. Nie można także używać
return
wewnątrz bloków try & catch.źródło
std::exception_ptr e; try { /*try block*/ } catch (...) { e = std::current_exception(); } /*finally block*/ if (e) std::rethrow_exception(e);
finally
bloku.Wymyśliłem
finally
makro, które może być używane prawie jak ¹finally
słowo kluczowe w Javie; korzysta zstd::exception_ptr
funkcji lambda i znajomych, astd::promise
więc wymagaC++11
lub jest wyższy; korzysta również z rozszerzenia GCC wyrażenia instrukcji złożonej , które jest również obsługiwane przez clang.OSTRZEŻENIE : wcześniejsza wersja tej odpowiedzi wykorzystywała inną implementację koncepcji z wieloma dodatkowymi ograniczeniami.
Najpierw zdefiniujmy klasę pomocnika.
Następnie jest rzeczywiste makro.
Można go użyć w następujący sposób:
Użycie
std::promise
sprawia, że bardzo łatwo jest go wdrożyć, ale prawdopodobnie wprowadza także sporo niepotrzebnych kosztów ogólnych, których można by uniknąć poprzez ponowne wdrożenie tylko potrzebnych funkcjonalnościstd::promise
.¹ CAVEAT: jest kilka rzeczy, które nie działają tak jak wersja Java
finally
. Z czubka mojej głowy:break
oświadczeniem od obrębietry
icatch()
„s bloków, ponieważ żyją w obrębie funkcji lambda;catch()
bloktry
: jest to wymaganie C ++;try
icatch()'s
, kompilacja zakończy się niepowodzeniem, ponieważfinally
makro rozwinie się do kodu, który będzie chciał zwrócić avoid
. Może to być, cóż, pustka wydana przez posiadanie pewnegofinally_noreturn
rodzaju makra.Podsumowując, nie wiem, czy sam bym tego użył, ale fajnie się z tym bawiłem. :)
źródło
catch(xxx) {}
blok na początkufinally
makra, gdzie xxx jest fałszywym typem tylko dla celów posiadania co najmniej jednego bloku catch.catch(...)
, prawda?xxx
w prywatnej przestrzeni nazw, która nigdy nie będzie używana.Mam przypadek użycia, w którym moim zdaniem
finally
powinna być całkowicie akceptowalna część języka C ++ 11, ponieważ uważam, że łatwiej jest go czytać z punktu widzenia przepływu. Mój przypadek użycia to łańcuch wątków konsumenta / producenta, w którymnullptr
na końcu przebiegu wysyłany jest wartownik, aby zamknąć wszystkie wątki.Jeśli C ++ to obsługuje, chciałbyś, aby Twój kod wyglądał następująco:
Myślę, że jest to bardziej logiczne niż umieszczenie deklaracji końcowej na początku pętli, ponieważ ma ona miejsce po zakończeniu pętli ... ale jest to myślenie życzeniowe, ponieważ nie możemy tego zrobić w C ++. Zauważ, że kolejka
downstream
jest połączona z innym wątkiem, więc nie możesz umieścić wartownikapush(nullptr)
w niszczycielu,downstream
ponieważ w tym momencie nie można go zniszczyć ... musi pozostać przy życiu, dopóki drugi wątek nie otrzymanullptr
.Oto, jak użyć klasy RAII z lambda, aby zrobić to samo:
a oto jak z niego korzystasz:
źródło
Jak wiele osób stwierdziło, rozwiązaniem jest użycie funkcji C ++ 11, aby w końcu uniknąć blokowania. Jedną z funkcji jest
unique_ptr
.Oto odpowiedź Mephane napisana przy użyciu wzorów RAII.
Więcej informacji na temat korzystania z Unique_ptr w kontenerach biblioteki standardowej C ++ znajduje się tutaj
źródło
Chciałbym przedstawić alternatywę.
Jeśli chcesz, aby blok zawsze był wywoływany zawsze, po prostu wstaw go po ostatnim bloku catch (prawdopodobnie powinien to być
catch( ... )
złapany nieznany wyjątek)Jeśli chcesz, aby blok był ostatnią rzeczą, którą należy zrobić po zgłoszeniu wyjątku, możesz użyć zmiennej lokalnej typu boolean - przed uruchomieniem ustaw wartość false i ustaw prawdziwe przypisanie na samym końcu bloku try, a następnie po sprawdzeniu bloku catch dla zmiennej wartość:
źródło
Myślę również, że RIIA nie jest w pełni użytecznym zamiennikiem obsługi wyjątków i posiadania w końcu. BTW, myślę też, że RIIA to zła nazwa dookoła. Te rodzaje zajęć nazywam „dozorcami” i używam ich dużo. W 95% przypadków nie inicjują ani nie zdobywają zasobów, wprowadzają pewne zmiany na podstawie zakresu lub biorą coś już skonfigurowanego i upewniają się, że jest zniszczone. Jest to oficjalny Internet z obsesją na punkcie wzorca, którego używam, nawet sugerując, że moje imię może być lepsze.
Po prostu nie sądzę, aby uzasadnione było wymaganie, aby każda skomplikowana konfiguracja jakiejś listy ad hoc rzeczy musiała mieć napisaną klasę, aby ją zawierała, aby uniknąć komplikacji podczas czyszczenia wszystkiego w obliczu konieczności złapania wielu typy wyjątków, jeśli coś pójdzie nie tak. Doprowadziłoby to do wielu zajęć ad hoc, które inaczej nie byłyby konieczne.
Tak, dobrze jest w przypadku klas zaprojektowanych do zarządzania konkretnym zasobem lub ogólnych, które są zaprojektowane do obsługi zestawu podobnych zasobów. Ale nawet jeśli wszystkie zaangażowane rzeczy mają takie opakowania, koordynacja czyszczenia może nie być prostym wywołaniem niszczycieli w odwrotnej kolejności.
Wydaje mi się, że C ++ ma w końcu sens. Mam na myśli, Jezu, w ciągu ostatnich dziesięcioleci przyklejono do niego tak wiele drobiazgów, że wydaje się, że dziwni ludzie nagle stali się konserwatystami w stosunku do czegoś w końcu, co może być całkiem przydatne i prawdopodobnie nic tak skomplikowanego jak niektóre inne rzeczy, które zostały dodano (choć to tylko zgadywanie).
źródło
źródło
finally
nie ma miejsca.