Generischer Resourceholder
-
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..
-
cooky451 schrieb:
Nein.
Das darfst du gerne etwas ausführlicher begründen
cooky451 schrieb:
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)
Dies ist mir bewusst, allerdings ist ein Mutex ein schönes Beispiel für ein Objekt, das eine Ressource darstellt und auf jedem (C++11) Compiler vorhanden ist. So musste ich nicht ewig damit verbringen, da irgendwas zusammen zu schustern.
cooky451 schrieb:
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.
Gerne.
-
lernwilliger schrieb:
Das darfst du gerne etwas ausführlicher begründen
Abgesehen davon, dass da einiges fehlt (move constructor, move assignment, ...), hat das Konzept so immer noch die alten Probleme. Dein Wrapper besitzt die Ressource nicht, sie kennt sie nur. Sieht man daran, dass du nur eine Referenz speicherst.
lernwilliger schrieb:
Dies ist mir bewusst, allerdings ist ein Mutex ein schönes Beispiel für ein Objekt, das eine Ressource darstellt und auf jedem (C++11) Compiler vorhanden ist.
Hehe, du modellierst da aber gerade einen lock, keine mutex. Das mutex Objekt gibt's doch schon.
std::mutex zu kapseln wäre allerdings auch sinnlos, da wurde RAII doch schon umgesetzt. Doppelt hält hier nicht besser. FILE* ist schon ein besseres Beispiel, oder HANDLE aus der WinAPI, oder Sockets bzw. POSIX Style file Deskriptoren. (Das sind letztlich ints).