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:
// A class with implements RAII
class lock
{
mutex &m_;
public:
lock(mutex &m)
: m_(m)
{
m.acquire();
}
~lock()
{
m_.release();
}
};
// A class which uses 'mutex' and 'lock' objects
class foo
{
mutex mutex_; // mutex for locking 'foo' object
public:
void bar()
{
lock scopeLock(mutex_); // lock object.
foobar(); // an operation which may throw an exception
// scopeLock will be destructed even if an exception
// occurs, which will release the mutex and allow
// other functions to lock the object and run.
}
};
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.
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.
void someFunc()
{
DB db("DBDesciptionString");
// Use the db object.
} // db goes out of scope and destructor closes the connection.
// This happens even in the presence of exceptions.
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ć.
void someFunc()
{
DB db = new DB("DBDesciptionString");
try
{
// Use the db object.
}
finally
{
// Can not rely on finaliser.
// So we must explicitly close the connection.
try
{
db.close();
}
catch(Throwable e)
{
/* Ignore */
// Make sure not to throw exception if one is already propagating.
}
}
}
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
// 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:
namespace detail { //adapt to your "private" namespace
template <typename F>
struct FinalAction {
FinalAction(F f) : clean_{f} {}
~FinalAction() { if(enabled_) clean_(); }
void disable() { enabled_ = false; };
private:
F clean_;
bool enabled_{true}; }; }
template <typename F>
detail::FinalAction<F> finally(F f) {
return detail::FinalAction<F>(f); }
przykład zastosowania:
#include <iostream>
int main() {
int* a = new int;
auto delete_a = finally([a] { delete a; std::cout << "leaving the block, deleting a!\n"; });
std::cout << "doing something ...\n"; }
wyjście będzie:
doing something...
leaving the block, deleting a!
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:
[...]
auto precision = std::cout.precision();
auto set_precision_back = finally( [precision, &std::cout]() { std::cout << std::setprecision(precision); } );
std::cout << std::setprecision(3);
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:
//strong guarantee
void copy_to_all(BIGobj const& a) {
first_.push_back(a);
auto undo_first_push = finally([first_&] { first_.pop_back(); });
second_.push_back(a);
auto undo_second_push = finally([second_&] { second_.pop_back(); });
third_.push_back(a);
//no necessary, put just to make easier to add containers in the future
auto undo_third_push = finally([third_&] { third_.pop_back(); });
undo_first_push.disable();
undo_second_push.disable();
undo_third_push.disable(); }
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.
#include <iostream>
int main() {
int* a = new int;
struct Delete_a_t {
Delete_a_t(int* p) : p_(p) {}
~Delete_a_t() { delete p_; std::cout << "leaving the block, deleting a!\n"; }
int* p_;
} delete_a(a);
std::cout << "doing something ...\n"; }
FinalAction
jest zasadniczo taki sam jak popularny ScopeGuard
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.
dlaczego nawet zarządzane języki zapewniają ostateczny blok, mimo że i tak zasoby są automatycznie zwalniane przez moduł wyrzucania elementów bezużytecznych?
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...
RAII przenosi odpowiedzialność za bezpieczeństwo wyjątku od użytkownika obiektu na projektanta
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.
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
template <typename TCode, typename TFinallyCode>
inline void with_finally(const TCode &code, const TFinallyCode &finally_code)
{
try
{
code();
}
catch (...)
{
try
{
finally_code();
}
catch (...) // Maybe stupid check that finally_code mustn't throw.
{
std::terminate();
}
throw;
}
finally_code();
}
Miejmy nadzieję, że kompilator zoptymalizuje powyższy kod.
Teraz możemy napisać taki kod:
with_finally(
[&]()
{
try
{
// Doing some stuff that may throw an exception
}
catch (const exception1 &)
{
// Handling first class of exceptions
}
catch (const exception2 &)
{
// Handling another class of exceptions
}
// Some classes of exceptions can be still unhandled
},
[&]() // finally
{
// This code will be executed in all three cases:
// 1) exception was not thrown at all
// 2) exception was handled by one of the "catch" blocks above
// 3) exception was not handled by any of the "catch" block above
}
);
Jeśli chcesz, możesz zawinąć ten idiom w makra „spróbuj - w końcu”:
// Please never throw exception below. It is needed to avoid a compilation error
// in the case when we use "begin_try ... finally" without any "catch" block.
class never_thrown_exception {};
#define begin_try with_finally([&](){ try
#define finally catch(never_thrown_exception){throw;} },[&]()
#define end_try ) // sorry for "pascalish" style :(
Teraz blok „w końcu” jest dostępny w C ++ 11:
begin_try
{
// A code that may throw
}
catch (const some_exception &)
{
// Handling some exceptions
}
finally
{
// A code that is always executed
}
end_try; // Sorry again for this ugly thing
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:
void function(std::vector<const char*> &vector)
{
int *arr1 = (int*)malloc(800*sizeof(int));
if (!arr1) { throw "cannot malloc arr1"; }
ON_FINALLY({ free(arr1); });
int *arr2 = (int*)malloc(900*sizeof(int));
if (!arr2) { throw "cannot malloc arr2"; }
ON_FINALLY({ free(arr2); });
vector.push_back("good");
ON_EXCEPTION({ vector.pop_back(); });
...
Przepraszamy za wykopanie tak starego wątku, ale w następującym uzasadnieniu występuje poważny błąd:
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.
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
void DoStuff(vector<string> input)
{
list<Foo*> myList;
try
{
for (int i = 0; i < input.size(); ++i)
{
Foo* tmp = new Foo(input[i]);
if (!tmp)
throw;
myList.push_back(tmp);
}
DoSomeStuff(myList);
}
finally
{
while (!myList.empty())
{
delete myList.back();
myList.pop_back();
}
}
}
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ę:
void DoStuff(vector<string> input)
{
list<Foo*> myList;
try
{
for (int i = 0; i < input.size(); ++i)
{
Foo* tmp = new Foo(input[i]);
if (!tmp)
throw;
myList.push_back(tmp);
}
DoSomeStuff(myList);
}
catch(...)
{
}
while (!myList.empty())
{
delete myList.back();
myList.pop_back();
}
}
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.
new
nie zwraca NULL, zamiast tego zgłasza wyjątek
std::shared_ptr
i std::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;
int CMyApp::Run()
{
__try
{
int i = CWinApp::Run();
m_Exitok = MAGIC_EXIT_NO;
return i;
}
__finally
{
if (m_Exitok != MAGIC_EXIT_NO)
FaultHandler();
}
}
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.
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 z finally
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:
#include <gsl/gsl_util.h>
void example()
{
int handle = get_some_resource();
auto handle_clean = gsl::finally([&handle] { clean_that_resource(handle); });
// Do a lot of stuff, return early and throw exceptions.
// clean_that_resource will always get called.
}
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()
brak disable()
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.
Nie bardzo, ale możesz je naśladować do pewnego stopnia, na przykład:
int * array = new int[10000000];
try {
// Some code that can throw exceptions
// ...
throw std::exception();
// ...
} catch (...) {
// The finally-block (if an exception is thrown)
delete[] array;
// re-throw the exception.
throw;
}
// The finally-block (if no exception was thrown)
delete[] array;
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.
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 z std::exception_ptr
funkcji lambda i znajomych, a std::promise
więc wymaga C++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.
#include <future>
template <typename Fun>
class FinallyHelper {
template <typename T> struct TypeWrapper {};
using Return = typename std::result_of<Fun()>::type;
public:
FinallyHelper(Fun body) {
try {
execute(TypeWrapper<Return>(), body);
}
catch(...) {
m_promise.set_exception(std::current_exception());
}
}
Return get() {
return m_promise.get_future().get();
}
private:
template <typename T>
void execute(T, Fun body) {
m_promise.set_value(body());
}
void execute(TypeWrapper<void>, Fun body) {
body();
}
std::promise<Return> m_promise;
};
template <typename Fun>
FinallyHelper<Fun> make_finally_helper(Fun body) {
return FinallyHelper<Fun>(body);
}
Następnie jest rzeczywiste makro.
#define try_with_finally for(auto __finally_helper = make_finally_helper([&] { try
#define finally }); \
true; \
({return __finally_helper.get();})) \
/***/
Można go użyć w następujący sposób:
void test() {
try_with_finally {
raise_exception();
}
catch(const my_exception1&) {
/*...*/
}
catch(const my_exception2&) {
/*...*/
}
finally {
clean_it_all_up();
}
}
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ści std::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ębie try
i catch()
„s bloków, ponieważ żyją w obrębie funkcji lambda;catch()
blok try
: jest to wymaganie C ++;try
i catch()'s
, kompilacja zakończy się niepowodzeniem, ponieważ finally
makro rozwinie się do kodu, który będzie chciał zwrócić a void
. Może to być, cóż, pustka wydana przez posiadanie pewnego finally_noreturn
rodzaju makra.Podsumowując, nie wiem, czy sam bym tego użył, ale fajnie się z tym bawiłem. :)
catch(xxx) {}
blok na początku finally
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órym nullptr
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:
extern Queue downstream, upstream;
int Example()
{
try
{
while(!ExitRequested())
{
X* x = upstream.pop();
if (!x) break;
x->doSomething();
downstream.push(x);
}
}
finally {
downstream.push(nullptr);
}
}
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ć wartownika push(nullptr)
w niszczycielu, downstream
ponieważ w tym momencie nie można go zniszczyć ... musi pozostać przy życiu, dopóki drugi wątek nie otrzyma nullptr
.
Oto, jak użyć klasy RAII z lambda, aby zrobić to samo:
class Finally
{
public:
Finally(std::function<void(void)> callback) : callback_(callback)
{
}
~Finally()
{
callback_();
}
std::function<void(void)> callback_;
};
a oto jak z niego korzystasz:
extern Queue downstream, upstream;
int Example()
{
Finally atEnd([](){
downstream.push(nullptr);
});
while(!ExitRequested())
{
X* x = upstream.pop();
if (!x) break;
x->doSomething();
downstream.push(x);
}
}
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.
#include <vector>
#include <memory>
#include <list>
using namespace std;
class Foo
{
...
};
void DoStuff(vector<string> input)
{
list<unique_ptr<Foo> > myList;
for (int i = 0; i < input.size(); ++i)
{
myList.push_back(unique_ptr<Foo>(new Foo(input[i])));
}
DoSomeStuff(myList);
}
Więcej informacji na temat korzystania z Unique_ptr w kontenerach biblioteki standardowej C ++ znajduje się tutaj
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)
try{
// something that might throw exception
} catch( ... ){
// what to do with uknown exception
}
//final code to be called always,
//don't forget that it might throw some exception too
doSomeCleanUp();
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ść:
bool generalAppState = false;
try{
// something that might throw exception
//the very end of try block:
generalAppState = true;
} catch( ... ){
// what to do with uknown exception
}
//final code to be called only when exception was thrown,
//don't forget that it might throw some exception too
if( !generalAppState ){
doSomeCleanUpOfDirtyEnd();
}
//final code to be called only when no exception is thrown
//don't forget that it might throw some exception too
else{
cleanEnd();
}
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).
try
{
...
goto finally;
}
catch(...)
{
...
goto finally;
}
finally:
{
...
}
finally
nie ma miejsca.