Unterstützt C ++ ' finally " -Blöcke?
Was ist der RAII-Sprache ?
Was ist der Unterschied zwischen der RAII-Sprache von C ++ und der using-Anweisung von C # ? ?
Unterstützt C ++ ' finally " -Blöcke?
Was ist der RAII-Sprache ?
Was ist der Unterschied zwischen der RAII-Sprache von C ++ und der using-Anweisung von C # ? ?
Antworten:
Nein, C ++ unterstützt keine 'finally'-Blöcke. Der Grund dafür ist, dass C ++ stattdessen RAII unterstützt: "Resource Acquisition Is Initialization" - ein schlechter Name † für ein wirklich nützliches Konzept.
Die Idee ist, dass der Destruktor eines Objekts für die Freigabe von Ressourcen verantwortlich ist. Wenn das Objekt eine automatische Speicherdauer hat, wird der Destruktor des Objekts aufgerufen, wenn der Block, in dem es erstellt wurde, beendet wird - auch wenn dieser Block bei Vorhandensein einer Ausnahme beendet wird. Hier ist die Erklärung von Bjarne Stroustrup des Themas.
Eine häufige Verwendung für RAII ist das Sperren eines Mutex:
// 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 vereinfacht auch die Verwendung von Objekten als Mitglieder anderer Klassen. Wenn die besitzende Klasse 'zerstört wird, wird die von der RAII-Klasse verwaltete Ressource freigegeben, da der Destruktor für die von RAII verwaltete Klasse als Ergebnis aufgerufen wird. Dies bedeutet, dass Sie bei Verwendung von RAII für alle Mitglieder einer Klasse, die Ressourcen verwalten, einen sehr einfachen, möglicherweise sogar standardmäßigen Destruktor für die Eigentümerklasse verwenden können, da die Lebensdauer der Mitgliederressourcen nicht manuell verwaltet werden muss . (Vielen Dank an Mike B für diesen Hinweis.)
Für diejenigen, die mit C # oder VB.NET vertraut sind, können Sie erkennen, dass RAII der deterministischen Zerstörung von .NET mit IDisposable- und 'using'-Anweisungen ähnlich ist . In der Tat sind die beiden Methoden sehr ähnlich. Der Hauptunterschied besteht darin, dass RAII jede Art von Ressource - einschließlich Speicher - deterministisch freigibt. Bei der Implementierung von IDisposable in .NET (auch in der .NET-Sprache C ++ / CLI) werden Ressourcen mit Ausnahme des Speichers deterministisch freigegeben. In .NET wird der Speicher nicht deterministisch freigegeben. Der Speicher wird nur während der Speicherbereinigungszyklen freigegeben.
† Einige Leute glauben, dass "Zerstörung ist Ressourcenabgabe" ein genauerer Name für die RAII-Sprache ist.
In C ++ ist das schließlich wegen RAII NICHT erforderlich.
RAII überträgt die Verantwortung für die Ausnahmesicherheit vom Benutzer des Objekts auf den Designer (und Implementierer) des Objekts. Ich würde argumentieren, dass dies der richtige Ort ist, da Sie dann die Ausnahmesicherheit nur einmal korrekt ausführen müssen (im Design / in der Implementierung). Wenn Sie schließlich verwenden, müssen Sie die Ausnahmesicherheit jedes Mal korrekt einstellen, wenn Sie ein Objekt verwenden.
Auch IMO sieht der Code ordentlicher aus (siehe unten).
Beispiel:
Ein Datenbankobjekt. Um sicherzustellen, dass die DB-Verbindung verwendet wird, muss sie geöffnet und geschlossen werden. Mit RAII kann dies im Konstruktor / Destruktor erfolgen.
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.
Die Verwendung von RAII macht die korrekte Verwendung eines DB-Objekts sehr einfach. Das DB-Objekt wird sich durch die Verwendung eines Destruktors korrekt schließen, unabhängig davon, wie wir versuchen, es zu missbrauchen.
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.
}
}
}
Bei der endgültigen Verwendung wird die korrekte Verwendung des Objekts an den Benutzer des Objekts delegiert. dh Es liegt in der Verantwortung des Objektbenutzers, die DB-Verbindung korrekt explizit zu schließen. Jetzt könnten Sie argumentieren, dass dies im Finalisierer möglich ist, aber Ressourcen möglicherweise nur eine begrenzte Verfügbarkeit oder andere Einschränkungen aufweisen. Daher möchten Sie im Allgemeinen die Freigabe des Objekts steuern und sich nicht auf das nicht deterministische Verhalten des Garbage Collectors verlassen.
Auch dies ist ein einfaches Beispiel.
Wenn Sie mehrere Ressourcen haben, die freigegeben werden müssen, kann der Code kompliziert werden.
Eine detailliertere Analyse finden Sie hier: http://accu.org/index.php/journals/236
// Make sure not to throw exception if one is already propagating.
Aus diesem Grund ist es für C ++ - Destruktoren wichtig, keine Ausnahmen auszulösen.
RAII ist normalerweise besser, aber Sie können leicht die endgültige Semantik in C ++ haben. Mit einer winzigen Menge Code.
Außerdem geben die C ++ Core Guidelines endlich.
Hier ist ein Link zur Implementierung von GSL Microsoft und ein Link zur Implementierung von Martin Moene
Bjarne Stroustrup sagte mehrmals, dass alles, was in der GSL enthalten ist, irgendwann in den Standard aufgenommen werden soll. Es sollte also eine zukunftssichere Möglichkeit sein, sie endlich zu verwenden .
Sie können sich leicht implementieren, wenn Sie möchten, lesen Sie weiter.
In C ++ 11 erlaubt RAII und Lambdas endlich einen General zu machen:
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); }
Anwendungsbeispiel:
#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"; }
Die Ausgabe lautet:
doing something...
leaving the block, deleting a!
Persönlich habe ich dies einige Male verwendet, um sicherzustellen, dass der POSIX-Dateideskriptor in einem C ++ - Programm geschlossen wird.
Eine echte Klasse zu haben, die Ressourcen verwaltet und so jegliche Art von Lecks vermeidet, ist normalerweise besser, aber dies ist schließlich in den Fällen nützlich, in denen das Erstellen einer Klasse wie ein Overkill klingt.
Außerdem habe ich , wie es besser als andere Sprachen endlich da , wenn verwendet , natürlich Sie den Schließcode in der Nähe der Öffnungscode (in meinem Beispiel die schreiben neue und löschen ) und Zerstörung folgt Bau in LIFO - Reihenfolge wie üblich in C ++. Der einzige Nachteil ist, dass Sie eine automatische Variable erhalten, die Sie nicht wirklich verwenden, und die Lambda-Syntax macht sie etwas verrauscht (in meinem Beispiel in der vierten Zeile sind nur das Wort finally und der {} -Block rechts von Bedeutung, die Ruhe ist im Wesentlichen Lärm).
Ein anderes Beispiel:
[...]
auto precision = std::cout.precision();
auto set_precision_back = finally( [precision, &std::cout]() { std::cout << std::setprecision(precision); } );
std::cout << std::setprecision(3);
Das Deaktivierungsmitglied ist nützlich, wenn das endgültige nur im Fehlerfall aufgerufen werden muss. Wenn Sie beispielsweise ein Objekt in drei verschiedenen Containern kopieren müssen, können Sie die Option zum endgültigen Rückgängigmachen jeder Kopie einrichten und deaktivieren, nachdem alle Kopien erfolgreich waren. Wenn die Zerstörung nicht geworfen werden kann, stellen Sie die starke Garantie sicher.
Beispiel deaktivieren :
//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(); }
Wenn Sie C ++ 11 nicht verwenden können, können Sie es schließlich noch haben , aber der Code wird etwas langwieriger. Definieren Sie einfach eine Struktur mit nur einem Konstruktor und einem Destruktor, der Konstruktor nimmt Verweise auf alles, was benötigt wird, und der Destruktor führt die Aktionen aus, die Sie benötigen. Dies ist im Grunde das, was das Lambda manuell macht.
#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
im Grunde dasselbe ist wie die populäre ScopeGuard
Redewendung, nur mit einem anderen Namen.
RAII erleichtert nicht nur die Bereinigung mit stapelbasierten Objekten, sondern ist auch nützlich, da dieselbe automatische Bereinigung erfolgt, wenn das Objekt Mitglied einer anderen Klasse ist. Wenn die besitzende Klasse zerstört wird, wird die von der RAII-Klasse verwaltete Ressource bereinigt, da der dtor für diese Klasse als Ergebnis aufgerufen wird.
Dies bedeutet, dass Sie, wenn Sie das RAII-Nirvana erreichen und alle Mitglieder einer Klasse RAII verwenden (wie intelligente Zeiger), mit einem sehr einfachen (möglicherweise sogar standardmäßigen) Dtor für die Eigentümerklasse davonkommen können, da diese nicht manuell verwaltet werden muss Lebensdauer der Mitgliederressourcen.
Warum bieten selbst verwaltete Sprachen einen endgültigen Block, obwohl Ressourcen vom Garbage Collector ohnehin automatisch freigegeben werden?
Tatsächlich brauchen Sprachen, die auf Garbage Collectors basieren, "endlich" mehr. Ein Garbage Collector zerstört Ihre Objekte nicht rechtzeitig, sodass Sie sich nicht darauf verlassen können, dass nicht speicherbezogene Probleme korrekt behoben werden.
In Bezug auf dynamisch zugewiesene Daten würden viele argumentieren, dass Sie Smart-Pointer verwenden sollten.
Jedoch...
RAII überträgt die Verantwortung für die Ausnahmesicherheit vom Benutzer des Objekts auf den Designer
Leider ist dies sein eigener Untergang. Alte C-Programmiergewohnheiten sterben schwer. Wenn Sie eine Bibliothek verwenden, die in C oder einem sehr C-Stil geschrieben ist, wurde RAII nicht verwendet. Wenn Sie nicht das gesamte API-Front-End neu schreiben, müssen Sie genau damit arbeiten. Dann beißt der Mangel an "endlich" wirklich.
CleanupFailedException
. Gibt es einen plausiblen Weg, um mit RAII ein solches Ergebnis zu erzielen?
SomeObject.DoSomething()
Methode aufruft und wissen möchte, ob sie (1) erfolgreich war, (2) ohne Nebenwirkungen fehlgeschlagen ist, (3) mit Nebenwirkungen fehlgeschlagen ist, auf die der Aufrufer vorbereitet ist , oder (4) fehlgeschlagen mit Nebenwirkungen, die der Anrufer nicht bewältigen kann. Nur der Anrufer wird wissen, mit welchen Situationen er fertig werden kann und welche nicht. Was der Anrufer braucht, ist eine Möglichkeit zu wissen, wie die Situation ist. Es ist schade, dass es keinen Standardmechanismus für die Bereitstellung der wichtigsten Informationen zu einer Ausnahme gibt.
Eine weitere "endgültige" Blockemulation mit C ++ 11 Lambda-Funktionen
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();
}
Hoffen wir, dass der Compiler den obigen Code optimiert.
Jetzt können wir Code wie folgt schreiben:
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
}
);
Wenn Sie möchten, können Sie diese Redewendung in "try - finally" -Makros einbinden:
// 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 :(
Jetzt ist der Block "finally" in C ++ 11 verfügbar:
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
Persönlich mag ich die "Makro" -Version von "finally" nicht und würde es vorziehen, die reine "with_finally" -Funktion zu verwenden, obwohl eine Syntax in diesem Fall sperriger ist.
Sie können den obigen Code hier testen: http://coliru.stacked-crooked.com/a/1d88f64cb27b3813
PS
Wenn Sie einen endgültigen Block in Ihrem Code benötigen , passen Scoped Guards oder ON_FINALLY / ON_EXCEPTION- Makros wahrscheinlich besser zu Ihren Anforderungen.
Hier ist ein kurzes Anwendungsbeispiel 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(); });
...
Es tut mir leid, dass ich einen so alten Thread ausgegraben habe, aber die folgenden Überlegungen enthalten einen schwerwiegenden Fehler:
RAII überträgt die Verantwortung für die Ausnahmesicherheit vom Benutzer des Objekts auf den Designer (und Implementierer) des Objekts. Ich würde argumentieren, dass dies der richtige Ort ist, da Sie dann die Ausnahmesicherheit nur einmal korrekt ausführen müssen (im Design / in der Implementierung). Wenn Sie schließlich verwenden, müssen Sie die Ausnahmesicherheit jedes Mal korrekt einstellen, wenn Sie ein Objekt verwenden.
Meistens müssen Sie sich mit dynamisch zugewiesenen Objekten, dynamischer Anzahl von Objekten usw. befassen. Innerhalb des Try-Blocks kann ein Code viele Objekte erstellen (wie viele zur Laufzeit bestimmt werden) und Zeiger darauf in einer Liste speichern. Dies ist kein exotisches Szenario, aber sehr häufig. In diesem Fall möchten Sie Dinge wie schreiben
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();
}
}
}
Natürlich wird die Liste selbst zerstört, wenn der Gültigkeitsbereich verlassen wird, aber das würde die von Ihnen erstellten temporären Objekte nicht bereinigen.
Stattdessen müssen Sie den hässlichen Weg gehen:
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();
}
}
Außerdem: Warum bieten selbst verwaltete Sprachen einen endgültigen Block, obwohl Ressourcen vom Garbage Collector ohnehin automatisch freigegeben werden?
Hinweis: Mit "endlich" können Sie mehr tun als nur die Freigabe des Speichers.
new
gibt nicht NULL zurück, sondern
std::shared_ptr
und std::unique_ptr
direkt in der stdlib.
FWIW, Microsoft Visual C ++ unterstützt schließlich try und wurde in der Vergangenheit in MFC-Apps verwendet, um schwerwiegende Ausnahmen abzufangen, die andernfalls zu einem Absturz führen würden. Beispielsweise;
int CMyApp::Run()
{
__try
{
int i = CWinApp::Run();
m_Exitok = MAGIC_EXIT_NO;
return i;
}
__finally
{
if (m_Exitok != MAGIC_EXIT_NO)
FaultHandler();
}
}
Ich habe dies in der Vergangenheit verwendet, um beispielsweise Sicherungen geöffneter Dateien vor dem Beenden zu speichern. Bestimmte JIT-Debugging-Einstellungen brechen diesen Mechanismus jedoch.
Wie in den anderen Antworten ausgeführt, kann C ++ finally
ähnliche Funktionen unterstützen. Die Implementierung dieser Funktionalität, die wahrscheinlich der Standardsprache am nächsten kommt, ist diejenige, die den C ++ - Kernrichtlinien beiliegt , einer Reihe von Best Practices für die Verwendung von C ++, die von Bjarne Stoustrup und Herb Sutter bearbeitet wurden. Eine Implementierung vonfinally
ist Teil der Guidelines Support Library (GSL). In allen Richtlinien wird die Verwendung von finally
empfohlen, wenn mit Schnittstellen alten Stils gearbeitet wird, und es gibt auch eine eigene Richtlinie mit dem Titel Verwenden Sie ein final_action-Objekt, um die Bereinigung auszudrücken, wenn kein geeignetes Ressourcenhandle verfügbar ist .
C ++ unterstützt also nicht nur finally
, es wird auch empfohlen, es in vielen gängigen Anwendungsfällen zu verwenden.
Eine beispielhafte Verwendung der GSL-Implementierung würde folgendermaßen aussehen:
#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.
}
Die Implementierung und Verwendung von GSL ist der in der Antwort von Paolo.Bolzoni sehr ähnlich . Ein Unterschied besteht darin, dass dem von erstellten Objekt gsl::finally()
der disable()
Aufruf fehlt . Wenn Sie diese Funktionalität benötigen (z. B. um die Ressource zurückzugeben, sobald sie zusammengestellt ist und keine Ausnahmen auftreten müssen), bevorzugen Sie möglicherweise die Implementierung von Paolo. Andernfalls kommt die Verwendung von GSL der Verwendung standardisierter Funktionen so nahe wie möglich.
Nicht wirklich, aber Sie können sie in gewissem Umfang emulieren, zum Beispiel:
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;
Beachten Sie, dass der finally-Block möglicherweise selbst eine Ausnahme auslöst, bevor die ursprüngliche Ausnahme erneut ausgelöst wird, wodurch die ursprüngliche Ausnahme verworfen wird. Dies ist genau das gleiche Verhalten wie in einem Java-Endblock. Sie können return
die Try & Catch-Blöcke auch nicht verwenden .
std::exception_ptr e; try { /*try block*/ } catch (...) { e = std::current_exception(); } /*finally block*/ if (e) std::rethrow_exception(e);
finally
Block heraus auszulösen .
Ich habe mir ein finally
Makro ausgedacht, das fast wie ¹ das finally
Schlüsselwort in Java verwendet werden kann. es nutzt std::exception_ptr
und Freunde, Lambda-Funktionen und std::promise
, so erfordert es C++11
oder darüber; Es verwendet auch die zusammengesetzte Anweisungsausdruck- GCC-Erweiterung, die auch von clang unterstützt wird.
WARNUNG : In einer früheren Version dieser Antwort wurde eine andere Implementierung des Konzepts mit vielen weiteren Einschränkungen verwendet.
Definieren wir zunächst eine Hilfsklasse.
#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);
}
Dann gibt es das eigentliche Makro.
#define try_with_finally for(auto __finally_helper = make_finally_helper([&] { try
#define finally }); \
true; \
({return __finally_helper.get();})) \
/***/
Es kann wie folgt verwendet werden:
void test() {
try_with_finally {
raise_exception();
}
catch(const my_exception1&) {
/*...*/
}
catch(const my_exception2&) {
/*...*/
}
finally {
clean_it_all_up();
}
}
Die Verwendung von std::promise
macht die Implementierung sehr einfach, führt aber wahrscheinlich auch zu unnötigem Overhead, der vermieden werden könnte, indem nur die erforderlichen Funktionen von neu implementiert werden std::promise
.
¹ CAVEAT: Es gibt einige Dinge, die nicht ganz so funktionieren wie die Java-Version von finally
. Aus dem Kopf:
break
Anweisung aus den Blöcken von try
und aus einer äußeren Schleife auszubrechen catch()
, da sie innerhalb einer Lambda-Funktion leben.catch()
Nach dem muss mindestens ein Block stehen try
: Es handelt sich um eine C ++ - Anforderung.try
und keine Rückgabe vorhanden ist, schlägt die catch()'s
Kompilierung fehl, da das finally
Makro zu Code erweitert wird, der a zurückgeben möchte void
. Dies könnte eine Leere sein, die durch eine Art finally_noreturn
Makro entsteht.Alles in allem weiß ich nicht, ob ich dieses Zeug jemals selbst benutzen würde, aber es hat Spaß gemacht, damit zu spielen. :) :)
catch(xxx) {}
Block am Anfang des finally
Makros einfügen, wobei xxx ein Scheintyp ist, nur um mindestens einen Catch-Block zu haben.
catch(...)
, nicht wahr ?
xxx
in einem privaten Namespace, der niemals verwendet wird.
Ich habe einen Anwendungsfall, in dem ich denke, finally
dass dies ein durchaus akzeptabler Teil der C ++ 11-Sprache sein sollte, da ich denke, dass es aus Sicht des Flusses einfacher zu lesen ist. Mein Anwendungsfall ist eine Consumer / Producer-Kette von Threads, nullptr
bei der am Ende des Laufs ein Sentinel gesendet wird, um alle Threads herunterzufahren.
Wenn C ++ dies unterstützt, soll Ihr Code folgendermaßen aussehen:
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);
}
}
Ich denke, dies ist logischer, als Ihre endgültige Deklaration an den Anfang der Schleife zu setzen, da sie nach dem Beenden der Schleife auftritt ... aber das ist Wunschdenken, weil wir es in C ++ nicht tun können. Beachten Sie, dass die Warteschlange downstream
mit einem anderen Thread verbunden ist, sodass Sie den Sentinel nicht push(nullptr)
in den Destruktor von downstream
einfügen können, da er zu diesem Zeitpunkt nicht zerstört werden kann. Er muss am Leben bleiben, bis der andere Thread den empfängt nullptr
.
So verwenden Sie eine RAII-Klasse mit Lambda, um dasselbe zu tun:
class Finally
{
public:
Finally(std::function<void(void)> callback) : callback_(callback)
{
}
~Finally()
{
callback_();
}
std::function<void(void)> callback_;
};
und so benutzt du es:
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);
}
}
Wie viele Leute angegeben haben, besteht die Lösung darin, C ++ 11-Funktionen zu verwenden, um endgültige Blockierungen zu vermeiden. Eines der Merkmale ist unique_ptr
.
Hier ist Mephanes Antwort, die mit RAII-Mustern geschrieben wurde.
#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);
}
Eine weitere Einführung in die Verwendung von unique_ptr mit C ++ Standard Library-Containern finden Sie hier
Ich möchte eine Alternative anbieten.
Wenn Sie möchten, dass finally block immer aufgerufen wird, setzen Sie es einfach nach dem letzten catch-Block (was wahrscheinlich sein sollte catch( ... )
, um eine nicht bekannte Ausnahme abzufangen).
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();
Wenn Sie beim Auslösen einer Ausnahme als letztes eine endgültige Blockierung durchführen möchten, können Sie eine boolesche lokale Variable verwenden. Vor dem Ausführen setzen Sie sie auf false und setzen die wahre Zuweisung ganz am Ende des try-Blocks. Nach dem catch-Block wird nach der Variablen gesucht Wert:
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();
}
Ich denke auch, dass RIIA kein voll nützlicher Ersatz für die Ausnahmebehandlung und ein endgültiges Problem ist. Übrigens denke ich auch, dass RIIA ein schlechter Name ist. Ich nenne diese Arten von Klassen "Hausmeister" und benutze sie eine Menge. In 95% der Fälle, in denen sie weder Ressourcen initialisieren noch erwerben, wenden sie Änderungen auf einer bestimmten Basis an oder nehmen etwas, das bereits eingerichtet wurde, und stellen sicher, dass es zerstört wird. Dies ist der offizielle Mustername, der vom Internet besessen ist. Ich werde missbraucht, weil ich sogar vorschlage, mein Name könnte besser sein.
Ich halte es einfach nicht für vernünftig zu verlangen, dass für jede komplizierte Einrichtung einer Ad-hoc-Liste von Dingen eine Klasse geschrieben werden muss, um sie zu enthalten, um Komplikationen beim Reinigen zu vermeiden, obwohl mehrere gefangen werden müssen Ausnahmetypen, wenn dabei etwas schief geht. Dies würde zu vielen Ad-hoc-Kursen führen, die sonst einfach nicht notwendig wären.
Ja, es ist in Ordnung für Klassen, die für die Verwaltung einer bestimmten Ressource ausgelegt sind, oder für allgemeine Klassen, die für die Verarbeitung einer Reihe ähnlicher Ressourcen ausgelegt sind. Aber selbst wenn alle beteiligten Dinge solche Wrapper haben, kann die Koordination der Bereinigung nicht nur ein einfacher Aufruf von Destruktoren in umgekehrter Reihenfolge sein.
Ich denke, es ist absolut sinnvoll für C ++, endlich eine zu haben. Ich meine, Herrgott, in den letzten Jahrzehnten wurden so viele Kleinigkeiten darauf geklebt, dass es den Anschein hat, als würden seltsame Leute plötzlich konservativ gegenüber so etwas wie endlich, was sehr nützlich sein könnte und wahrscheinlich nicht annähernd so kompliziert wie einige andere Dinge, die es gewesen sind fügte hinzu (obwohl das nur eine Vermutung von meiner Seite ist.)
try
{
...
goto finally;
}
catch(...)
{
...
goto finally;
}
finally:
{
...
}
finally
nicht der Fall ist.