Generischer Resourceholder
-
@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?
-
Sorry, aber der Code geht gar nicht. Was war jetzt dein Problem mit unique_handle?
-
Erstens dass es unique_handle nicht gibt, zweitens dass ich für jede einzelne Benutzung von unique_handle 20 Zeilen unnützen Code schreiben muss.
-
@nwp3
Ja man kann viel machen.
Nur diesesRESOURCEHOLDER
Makro ist so furchtbar dass mir eigentlich egal ist was da noch alles ginge und was nicht.Aber gehen wir nochmal zurück zum Anfang...
nwp3 schrieb:
...kann man ja das Resource holen im Konstruktor einer Klasse und das Freigeben im Destruktor machen und dann ein Objekt erstellen. Das hat den Vorteil, dass man sich ums Freigeben nicht mehr kümmern braucht. Solche Klassen zu schreiben ist irgendwie redundant, dafür gibt es sicherlich eine allgemeine Form.
Nö. Jedes mal den Initialisierungs- und Cleanup-Code zu wiederholen, wie du es mit deinem
RESOURCEHOLDER
Makro machst, DAS ist redundant.nwp3 schrieb:
//beispielhafte nutzung mit fantasiesyntax Resourceholder mrh<Mutex mutex, {mutex.aquire();}, {mutex.release();}>; ...
Was ist bitte mehr redundant (bzw. der schlechtere Code), ein Programm das eine eigene
MutexLock
Klasse definiert dann an 100 Stellen einfach nur mehrMutexLock l(mutex);
schreibt, oder eins das an 100 Stellen immer wieder den von dir gezeigten Code dupliziert?Bei den anderen Beispielen ist es das selbe: das sind Dinge, die du an vielen Stellen in deinem Programm brauchen wirst. D.h. es zahlt sich aus hier 10-20 Zeilen für eine spezialisierte RAII-Wrapper Klasse zu investieren.
Ich bin mit Templates nicht weit gekommen und weiß auch nicht wie ich Code per Template übergeben kann um die Klasse Resourceholder zu implementieren, wahrscheinlich braucht man Lambda-Expressions oder so. Gibt es dafür schon was fertiges? (std::resource_holder oder so)
Es gibt
unique_ptr
undshared_ptr
.Eine Klasse die mit Resourcen klarkommt die keine Zeiger sind gibt es nicht, das wäre die von Cooky erwähnte
unique_handle
Klasse. Sowas kann man sich selbst schreiben.Wobei das eigentlich egal ist, denn wie ich schon geschrieben habe halte ich das Wiederholen des Cleanup-Codes wie in deinen Beispielen für ziemlich pöse.
D.h. du musst sowieso noch zusätzlich eigenen RAII Klassen definieren.
Mit einer gutenunique_handle
Klasse ist das dann vielleicht nur mehr ein Typedef, bzw. der Aufwand reduziert sich durch die Verwendung vonunique_handle
deutlich. Die Wrapper-Klasse aber ganz einzusparen wäre ein böser Fehler.
-
@nwp3
1. Wenn du mit "nicht gibt" meinst dass es die Klasse nicht im Standard gibt dann ist das irgendwie ein schlechtes Argument, deinen Kram gibt's da auch nicht.
2. 20 Zeilen? O_oauto f = make_unique_handle(fopen(...), [] (FILE* f) { fclose(f); });
3. unique_handle gibt dir sogar die Möglichkeit den close-Funktor weg zu typedeffen. (Wobei dann ne extra Klasse sinn machen könnte.)
-
nwp3 schrieb:
Erstens dass es unique_handle nicht gibt,
Wenn du bereit bist Zeit in die Entwicklung eines äusserst fragwürdigen
RESOURCEHOLDER
Makro zu stecken, dann ist "gibts noch nicht" ja wohl kein Argument.
AnstattRESOURCEHOLDER
-Unsinn könntest du genau so gut was schreiben was dir das Schreiben von echten, vernünftigen RAII-Klassen erleichtert.zweitens dass ich für jede einzelne Benutzung von unique_handle 20 Zeilen unnützen Code schreiben muss.
-
Mit deinem
RESOURCEHOLDER
-Unsinn musst du den Cleanup-Code auch hundertfach wiederholen. Im prinzip ist das das selbe wie ON_SCOPE_EXIT, nur weniger mächtig und allgemein schlechter. -
Wie ich in meinem letzten Beitrag schon geschrieben habe ist das sowieso nicht der Weg. Wenn du etwas mehrfach brauchst, dann schreib dafür ne eigene RAII Klasse.
-
-
"unique_handle gibt es nicht" war nicht so gemeint. Ich meinte es gibt es nicht im Standard, also muss ich es selbst schreiben, und da würde bei mir genau RESOURCEHOLDER in make_unique_handle umbenannt rauskommen. Gib mal eine Implementation für make_unique_handle an, dann erkenne ich vielleicht auch den Vorteil.
auto f = make_unique_handle(fopen(...), [] (FILE* f) { fclose(f); });
kriege ich nicht zum Laufen.
Gleiches gilt für ON_SCOPE_EXIT: Wenn ich das selbst schreiben würde würde es ungefähr so aussehen:
#define ON_SCOPE_EXIT(EXITCODE) RESOURCEHOLDER(, , EXITCODE, unused__##STR(__LINE__))
Daher sehe ich auch überhaupt nicht ein wie ON_SCOPE_EXIT mächtiger sein soll. Ein Beispiel bitte.
Ich erkenne allerdings das Problem mit dem mehrfachen Code, wenn man mehrfach dasselbe in den RESOURCEHOLDER rein schreibt. Das wird einerseits dadurch abgeschwächt, dass nicht ich doppelten Code schreiben muss, sondern der Präprozessor, und zweitens der Compiler sicherlich erkennt, dass er den doppelten Code nur einmal braucht (vielleicht inline davor schreiben?). Wenn man den RESOURCEHOLDER einschränkt, dass er nur eine einzige Resource (=Variable) halten kann und ihn per Template<Variablentyp> initialisiert, dann hat man den Code garantiert nicht doppelt.
Da muss ich noch irgendwie sehen wie ich das hinkriege. Am Ende kommt wahrscheinlich die Implementation von make_unique_handle raus, aber dann habe ich es wenigstens verstanden.
-
Ich habe mich mal an einem
unique_handler
probiert, entspricht das ungefähr, dem was ihr im Sinn hattet? Und gibt es Verbesserungsvorschläge?#include <iostream> #include <thread> template <typename T, typename init_type, typename release_type> class unique_handler { T& t; init_type init; release_type release; public: unique_handler(T& t, init_type&& init, release_type&& release) : t(t), init(std::move(init)), release(std::move(release)) { init(t); } ~unique_handler() { release(t); } }; template <typename T, typename init_type, typename release_type> unique_handler<T, init_type, release_type> make_unique_handler(T& t, init_type&& init, release_type&& release) { return unique_handler<T, init_type, release_type>(t, std::move(init), std::move(release)); } int main(int argc, const char * argv[]) { std::mutex mutex; auto uh = make_unique_handler(mutex,[](std::mutex& mutex){ mutex.lock(); std::cout << "lock\n";}, [](std::mutex& mutex) { std::cout << "unlock\n"; mutex.unlock(); }); // insert code here... std::cout << "Hello, lock!\n"; return 0; }
-
Ohne die restlichen Seiten zu lessen und ohne zu wissen, ob bereits darauf verwiesen wurde: http://the-witness.net/news/2012/11/scopeexit-in-c11/ . Auch habe ich es nicht ausprobiert.
-
Nein. Abgesehen davon ist unique_handle nur bedingt dazu geeignet und gedacht einen lock darzustellen. Dafür gibt es, wie schon geschrieben, extra Dinge im Standard. (lock_guard, unique_lock) Ich werde wohl mal eine an unique_ptr angelehnte Implementierung posten, aber unique_ptr hat einige komische Tricks im Standard, die ich erstmal nachvollziehen muss.
@knivil Hättest du lieber mal die anderen Seiten gelesen..