Generischer Resourceholder
-
Naja, ich hab mich schon öfters in der Situation gefunden, dass ich so ein RAII Teil für eine Ressource benötigt hätte, die ich nur einmal im ganzen Projekt benutze. Ich hab die 10 min. jedes Mal investiert und es bisher noch nie bereut, weil ich den Code spätestens in einem anderen Projekt weiterverwenden konnte...
-
Ich denke man sollte immer abwägen wie gross der Aufwand ist wenn man etwas wiederverwendbares schreiben will, und wie hoch die Chance ist dass es auch wirklich nochmal zum Einsatz kommt.
Und ich habe diesbezüglich schon viel schreckliches gesehen - und zwar auf beiden Seiten der goldenen Mitte.Ich denke immer wieder mit Schrecken an Programme wo man die selben 3/5/100 Zeilen zigfach in EINEM Programm findet, weil der Programmierer zu doof/faul/... war irgendwo eine Funktion/Klasse/... dafür zu schreiben.
Genau so kenne ich aber auch einige Fälle wo wirklich viel Aufwand betrieben wurde um alles schön generisch und wiederverwendbar zu machen, und nichts davon wurde jemals irgendwo wieder verwendet.Also wenn ich irgendwo ein File schreibe, und keine Library habe die passende RAII File Klassen hat, dann schreib ich selbst eine. Die Chancen dass man sowas nochmal braucht stehen gut.
Wenn ich im Projekt X für den Kunden Y den Labelprinter Z ansteuere, und zwar über ein SDK vom Hersteller, das nur mit genau diesem einen Drucker kompatibel ist...
Und wenn ich dann an genau einer Stelle in dem Programm 1x den Drucker ansprechen muss...
Und nicht damit rechne noch jemals etwas mit diesem Drucker zu machen...
Dann werde ich vermutlich keine eigene RAII Klasse dafür schreiben.
Weil es sich eher nicht auszahlt.
-
Der Punkt ist, dass du quasi alle Fälle mit unique_handle sehr sauber erschlagen kannst. Ich weiß gar nicht ob's da schon was zu gibt, aber wenn nicht, werde ich das wohl mal für C++14 vorschlagen. Die Klasse muss eigentlich in den Standard.
-
+1 fuer scope(exit/failure).
-
cooky451 schrieb:
Der Punkt ist, dass du quasi alle Fälle mit unique_handle sehr sauber erschlagen kannst.
Könntest du kurz dessen Anwendung für die Beispiele des Threaderstellers (z.B.
FILE*
) aufzeigen?Kellerautomat schrieb:
+1 fuer scope(exit/failure).
Falsche Sprache. In C++ müsstest du dafür
ON_SCOPE_EXIT
benutzen. EinON_SCOPE_FAILURE
könnte man zwar mitstd::uncaught_exception()
hinfrickeln, aber ob sich das lohnt...
-
Nexus schrieb:
Falsche Sprache. In C++ müsstest du dafür
ON_SCOPE_EXIT
benutzen. EinON_SCOPE_FAILURE
könnte man zwar mitstd::uncaught_exception()
hinfrickeln, aber ob sich das lohnt...Ehm... Was du nicht sagst?
-
Nexus schrieb:
Kellerautomat schrieb:
+1 fuer scope(exit/failure).
Falsche Sprache. In C++ müsstest du dafür
ON_SCOPE_EXIT
benutzen. EinON_SCOPE_FAILURE
könnte man zwar mitstd::uncaught_exception()
hinfrickeln, aber ob sich das lohnt...Meep, geht nicht. http://www.gotw.ca/gotw/047.htm. Scope(failure) ist in C++ schlicht unmöglich korrekt hinzubekommen (ohne bool-Flag).
-
Kellerautomat schrieb:
Ehm... Was du nicht sagst?
Der Thread geht um "generische Resourceholder". Wenn du ohne Kommentare nur ein cooles "+1 fuer scope(exit/failure)" übrig hast, ist das relativ wenig hilfreich. Und in diesem Punkt muss ich cooky451 zustimmen: So wie auf Stackoverflow für
scope(exit)
geworben wird, wäre -- zumindest sobald man das mehrmals braucht -- ein richtiger RAII-Wrapper sinnvoller.alexandrescu schrieb:
Sagt der Artikel nicht nur, dass
uncaught_exception()
ungeeignet ist, um das Exception-Werfen in Destruktoren davon abhängig zu machen? Das Problem tritt ja bei verschachtelten Statements auf, wobeiscope(failure)
üblicherweise für lokale Variablen von Funktionen eingesetzt wird. Aber es bliebe wohl ein Hack.alexandrescu schrieb:
Scope(failure) ist in C++ schlicht unmöglich korrekt hinzubekommen (ohne bool-Flag).
Es scheint aber Implementierungen zu geben, z.B. die Bibliothek stack_unwinding. Der Entwickler sagt, er benutze ein plattformabhängiges
uncaught_exception_count()
, ich weiss nicht ob es mituncaught_exception()
genau gleich möglich wäre.
-
Nein, kann man nicht. uncaught_exception_count() wird aber gerade im isocpp Forum diskutiert und moeglicherweise mit C++14 standardisiert.
-
@Nexus
Du kannst jedes beliebige C++ Programm folgendermassen transformieren:// Alte main() in original_main() umbenennen und dann...: struct original_main_caller { int& m_rc; original_main_caller(int& rc) : m_rc(rc) {} ~original_main_caller() { m_rc = original_main(); } }; int main() { int rc = 0; try { original_main_caller omc(rc); throw 1; } catch (int) { return rc; } }
Jetzt gibt
uncaught_exception
immertrue
zurück und ist damit zu nichts mehr zu gebrauchen.Das Beispiel ist konstruiert, aber in ausreichen grossen Programmen finden sich früher oder später genug Stellen wo ein über
uncaught_exception
implementierteson_scope_failure
Mist bauen würde.Daraus folgt dass
uncaught_exception
grundsätzlich für nichts zu gebrauchen ist, es ist einfach nur ein Design-Fail.
-
cooky451 schrieb:
Der Punkt ist, dass du quasi alle Fälle mit unique_handle sehr sauber erschlagen kannst. Ich weiß gar nicht ob's da schon was zu gibt, aber wenn nicht, werde ich das wohl mal für C++14 vorschlagen. Die Klasse muss eigentlich in den Standard.
Ich sage ja nicht dass
unique_handle
eine schlechte Idee wäre. Ist sogar eine sehr gute Idee, ich würde sogar so weit gehen zu behaupten dass es ne Schande ist dass es nicht zugleich mitunique_ptr
definiert/standardisiert wurde.Ich sage nur, so lange wir kein
unique_handle
haben, ist es vollkommen OK ab und an malON_SCOPE_EXIT
zu verwenden.
Wenn sich die Fälle wo man dannON_SCOPE_EXIT
verwendet häufen, dann kann man sich überlegen vielleicht selbst eine mehr oder weniger vollständige/universelleunique_handle
Klasse zu schreiben.
Bis dahin verwende ich weiterhinON_SCOPE_EXIT
.
-
Nexus schrieb:
cooky451 schrieb:
Der Punkt ist, dass du quasi alle Fälle mit unique_handle sehr sauber erschlagen kannst.
Könntest du kurz dessen Anwendung für die Beispiele des Threaderstellers (z.B.
FILE*
) aufzeigen?Nun, es gibt ein paar Varianten wie man das implementieren kann, aber grundsätzlich könnte das z.B. so aussehen:
auto file = make_unique_handle(fopen(..), nullptr, [] (FILE* f) { fclose(f); });
Hier ist das handle unweigerlich mit der Lebenszeit gekoppelt. Wobei man bei FILE* natürlich auch unique_ptr benutzen könnte, unique_handle wird erst so richtig nützlich, wenn man eben keine Pointer mehr hat, oder "invalid" != nullptr ist.
@hustbaer Ich würde dir zustimmen, wenn unique_handle zu implementieren nicht nur etwa 20 Minuten dauern würde, und mein Code (WinAPI z.B.) ohne unique_handle nicht voll von ON_SCOPE_EXIT wäre.
-
*schnurch*
Wenn dein Code ohneunique_handle
voll vonON_SCOPE_EXIT
wäre, dann ist das ja wohl kein Fall von "wird nur 1-2 mal gebraucht" mehr, oder?
-
hustbaer schrieb:
*schnurch*
Wenn dein Code ohneunique_handle
voll vonON_SCOPE_EXIT
wäre, dann ist das ja wohl kein Fall von "wird nur 1-2 mal gebraucht" mehr, oder?Der Punkt ist ja, dass man zwar ständig ON_SCOPE_EXIT braucht, aber der Wrapper jedes Mal anders ist, sodass man ihn nie wiederverwenden kann.
Ich habe ein wenig rumexperimentiert und das hier ist bei rausgekommen:
#define RESOURCEHOLDER(DEFINITIONS, ENTRYCODE, EXITCODE, OBJECTNAME)\ struct RESOURCEHOLDER_##OBJECTNAME{\ DEFINITIONS\ RESOURCEHOLDER_##OBJECTNAME(){\ ENTRYCODE\ }\ ~RESOURCEHOLDER_##OBJECTNAME(){\ EXITCODE\ }\ }OBJECTNAME //Anwendungsbeispiel #include <iostream> #include <string> using std::cout; using std::string; int main(){ //2 Resourcen im selben scope: RESOURCEHOLDER(int i;, i = 5; cout << "RESOURCEHOLDER initialized\n";, cout << "RESOURCEHOLDER destroyed " << i << '\n';, rh); cout << rh.i << '\n'; rh.i += 2; RESOURCEHOLDER(double j;, j = 12.; cout << "RESOURCEHOLDER initialized\n";, cout << "RESOURCEHOLDER destroyed " << j << '\n';, rh2); cout << rh2.j << '\n'; rh2.j += 3; { //ich könnte schwören struct RESOURCEHOLDER_rh ist doppelt definiert, aber weder VS2010E noch gcc 4.3.4 stören sich dran RESOURCEHOLDER(string s;, s = "Hello world"; cout << "RESOURCEHOLDER initialized\n";, cout << "RESOURCEHOLDER destroyed " << s << '\n';, rh); cout << rh.s << '\n'; rh.s += "!!!"; } }
Ich wollte eigentlich was mit __LINE__ hinfrickeln, aber komischerweise funktioniert es auch ohne.
Es ist ein wenig gewöhnungsbedürftig die Kommas und Semikolons so zu mischen.
Außerdem kann man scheinbar keine Macros innerhalb von RESOURCEHOLDER() inklusive __LINE__ benutzen. Ansonsten ist es fast was ich wollte.
Wenn man es jetzt noch hin kriegt einen RESOURCEHOLDER und sonstige Macros innerhalb von einem anderen RESOURCEHOLDER zu benutzen wäre es benutzbar.
-
nwp3 schrieb:
//ich könnte schwören struct RESOURCEHOLDER_rh ist doppelt definiert, aber weder VS2010E noch gcc 4.3.4 stören sich dran
ist es auch, aber im inneren Blockscope (Lokale Klasse/Struct), und da Klassen/Structs auch Inline Methoden haben können, kann man sogar lokale Funktionen definieren
Hinweis: Blockscope != Functionscope
Functionscope haben nur Sprungmarken von goto Anweisungen, alles andere hat Blockscope und gilt nur in diesem Block
-
Anstatt den ResourceHolder für FILE* zu verwenden, kannst du auch einfach fstream nutzen, dabei wird nämlich automatisch die Datei geschlossen, wenn das Objekt zerstört wird.
Damit hättest du schonmal ein Problem weniger.
-
nwp3 schrieb:
hustbaer schrieb:
*schnurch*
Wenn dein Code ohneunique_handle
voll vonON_SCOPE_EXIT
wäre, dann ist das ja wohl kein Fall von "wird nur 1-2 mal gebraucht" mehr, oder?Der Punkt ist ja, dass man zwar ständig ON_SCOPE_EXIT braucht, aber der Wrapper jedes Mal anders ist, sodass man ihn nie wiederverwenden kann.
Kann ich mir nicht vorstellen.
Die Beispiele die du im Kopfbeitrag hier bringst sind auch alles ganz klare Fälle von "zahlt sich aus nen eigenen Wrapper zu schreiben".
-
Bevor ich den Augenkrebs da in meinem Code erdulde, würde ich auch eher ein Wrapper hinfrickeln!
Und das sagt jemand, der gerne in die Vergangenheit reisen würde, um sich selbst eine Backpfeife zu verpassen, weil sein vergangenes Ich Augen- und Gehirnkrebserregendes geschrieben hat.
-
Decimad schrieb:
Bevor ich den Augenkrebs da in meinem Code erdulde, würde ich auch eher ein Wrapper hinfrickeln!
-
Dass __LINE__ nicht (immer?) innerhalb eines Macros funktioniert ist ein Bug in Visual Studio und lässt sich umgehen. Der gcc hat das Problem nicht.
Man kann das Augenkrebsrisiko etwas reduzieren:
//versteckt man in einer headerdatei #define RESOURCEHOLDER(DEFINITIONS, ENTRYCODE, EXITCODE, OBJECTNAME)\ struct RESOURCEHOLDER_##OBJECTNAME{\ DEFINITIONS;\ RESOURCEHOLDER_##OBJECTNAME(){\ ENTRYCODE;\ }\ ~RESOURCEHOLDER_##OBJECTNAME(){\ EXITCODE;\ }\ }OBJECTNAME //Anwendungsbeispiel int main{ RESOURCEHOLDER(FILE *f, f = fopen("test.txt", "w"), fclose(f), rhf); fprintf(rhf.f, "Test"); }
So sehr unterscheidet es sich augenkrebstechnisch nicht mehr von einer for-Schleife. Man könnte natürlich noch
if (!f) throw "File open error"
in den entrycode stecken.
Wenn man einen RESOURCEHOLDER in einen shared_ptr stecken wollen würde bräuchte man eine Pointer-Variante:
#define RESOURCEHOLDER_PTR(DEFINITIONS, ENTRYCODE, EXITCODE, OBJECTNAME)\ struct RESOURCEHOLDER_##OBJECTNAME{\ ... }*OBJECTNAME = new RESOURCEHOLDER_##OBJECTNAME
Und kann es dann folgendermaßen verwenden:
RESOURCEHOLDER_PTR(int i, i = 5, i++, rhp); auto sp = std::shared_ptr<RESOURCEHOLDER_rhp>(rhp); sp->i++;
Dass es shared_ptr<RESOURCEHOLDER_rhp> und nicht shared_ptr<RESOURCEHOLDER> heißt stört natürlich.
struct RESOURCEHOLDER{ virtual ~RESOURCEHOLDER(){} }; #define RESOURCEHOLDER_PTR(DEFINITIONS, ENTRYCODE, EXITCODE, OBJECTNAME)\ struct RESOURCEHOLDER_##OBJECTNAME : public RESOURCEHOLDER{\ ... }*OBJECTNAME = new RESOURCEHOLDER_##OBJECTNAME
RESOURCEHOLDER_PTR(int i, i = 5, i++, rhp); auto sp = std::shared_ptr<RESOURCEHOLDER>(rhp); //funktioniert //sp->i++; //funktioniert nicht mehr :( set<RESOURCEHOLDER *> s; s.insert(rhp); //funktioniert
Ganz böses Vodoo?