weak_ptr und bind/functor mit "nop if expired" semantik
-
Ich bräuchte für ein Event/Callback-System die Möglichkeit einen Funktor zu basteln, der aus einem Member-Function-Pointer und einen weak_ptr als "this" besteht.
Die Semantik soll dabei sein dass der Aufruf des Funktors einfach nichts tut, falls der weak_ptr expired ist.Gibt es da irgend etwas fertiges? Oder gibt es eine einfache Möglichkeit das selbst zu programmieren, ohne das ganze Forwarding ala boost::function selbst implementieren zu müssen?
Zur Verfügung stehen mir die Boost 1.38.0 (keine Zeit das Projekt auf die Schnelle noch auf eine neuere Boost umzustellen), und als Compiler kommt Visual C++ 2005 zum Einsatz. Es stehen also auch keine C++0x Features zur Verfügung.
p.S.: was ich nicht dazugeschrieben habe: der weak_ptr muss vor dem Aufruf in einen shared_ptr konvertiert werden (lock()) der dann während des Aufrufs gehalten werden muss, damit garantiert ist, dass das Objekt nicht während des Aufrufs einfach "verschwinden" kann.
-
*push*
-
Ich denke, das wird in C++98 schwierig wegen des Forwarding-Problems. Also wirst du wahrscheinlich um mehrere
operator()-Überladungen nicht herumkommen...Gibt es irgendwelche Einschränkungen, denen die Memberfunktionen unterliegen? Maximale Parameterzahl, irgendeine Gemeinsamkeit,
const-Qualifizierung, oder musst du wirklich 100% generisch arbeiten?Ich habe zuerst an
boost::signalund dessen Combiner gedacht, allerdings müsste dafür der Rückgabetyp immer vom gleichen Typ sein.
-
Ich muss nicht 100% generisch arbeiten, in diesem Projekt brauche ich nur sehr wenige Signaturen.
Ich habe jetzt eine Klasse gebastelt die als ersten Parameter einen weak_ptr nimmt/erwartet, diesen Lockt, falls expired nix tut, und sonst den darin gespeicherten Funktor aufruft, mit Weiterleitung perconst T&.
Für das aktuelle Projekt ausreichend.Ist allerdings schon das 3. mal oder so, dass ich sowas brauche, und bisher hab' ich entweder drumrum gearbeitet, oder eben eine "good-for-this-project-only" Lösung gestrickt. Daher wäre es fein mal was wiederverwendbares zu basteln.
Was mich noch an der aktuellen Lösung stört: man muss, wenn man einen Member-Funktions-Zeiger an die Wrapper-Klasse übergibt, diesen zuerst mit boost:bind/boost::mem_fn in einen "normalen" Funktor verwandeln, da mein Wrapper keine Member-Funktions-Zeiger verdauen kann.
-
Wie willst du denn dann später feststellen, wann der Eintrag in deiner Liste gelöscht werden darf?
Hast du dann nicht zwangsläufig ein Speicherleck?
(Zumindest bis die Liste gelöscht wird und das wird vermutlich nicht so
oft sein bzw. sogar nur bei Beendigung des Programms)Sollte man in dem Fall nicht über das Design des Programms nachdenken?
-
Nexus schrieb:
Ich habe zuerst an
boost::signalund dessen Combiner gedacht, allerdings müsste dafür der Rückgabetyp immer vom gleichen Typ sein.Nach meinem Verständnis fordert nop den Rückgabewert
void.
-
@Speicherleck?:
Welche Liste?
Zur Erklärung:
Bei mir sieht das inetwa so aus:
class EinDing { // ... void PostConstructor() // wird aufgerufen wenn schon ein shared_ptr auf "this" existiert { m_eventConnection = EinEvent.Connect(shared_from_this(), &EinDing::DerHandlerDesDings); } EventConnection m_eventConnection; Irgendwas m_member1; Irgendwas m_member2; }EventConnection representiert dabei eben die Connection, d.h. wenn EventConnection sterben geht, dann geht auch die Connection sterben, d.h. der Funktor wird nimmer aufgerufen. Darum kümmert sich der Destruktor von EventConnection.
Ich kann logischerweise keinen "starken" shared_ptr in die EventConnection binden, da sonst ein Zyklus entstehen würde -> leak.
Ein roher Zeiger wäre eine Möglichkeit. Und die wäre in einem Single-Threaded-Modell auch ausreichend (gerade eben so). Hat zwar auch ein paar Tücken, da das Objekt im eigenen Destruktor den Event triggern könnte, was dazu führt dass der eigene Handler nochmals ausgeführt würde - mitten aus dem Destruktor raus. Etwas was man nicht unbedingt erwartet.
Bzw. schlimmer: es könnte sogar der Destruktor eines Members welches vor m_eventConnection zuerstört wird noch den Event triggern, z.B. der Destruktor von m_member1. In dem Moment wurde m_member2 allerdings schon zerstört. Wenn der Handler nun auf m_member2 zugreift ... eieiei. Alles ganz hässlich.
Wenn man davon ausgeht dass Destruktoren keine Events triggern dürfen, könnte man aber damit leben. Bzw. einfach alle EventConnection immer als letzte Member setzen und beten.Nu gibt's aber mehrere Threads, und die dürfen lustig Events feuern. Und genau das ist der Moment wo der Affe ins Wasser springt.
Während z.B. gerade m_member2 und m_member1 zerstört werden, triggert ein anderer Thread den Event. m_eventConnection wurde noch nicht zerstört, die Connection besteht noch, der Handler wird noch aufgerufen.
Und zwar im "event-triggernden" Thread, während in einem anderen Thread gerade genüsslich das Objekt zerstört wird. -> BUMMWenn der Handler nun allerdings folgendes macht...
void HandlerWrapper(weak_ptr<T> const& that, int blah) { shared_ptr<T> strongThat = that.lock(); if (strongThat) strongThat->Handler(blah); }...ist das Problem gegessen.
Der Destruktor von EinDing kann ja erst loslaufen wenn (nachdem) bereits KEIN shared_ptr mehr auf das Objekt existiert. Ab dem Moment wird that.lock() einen "leeren" shared_ptr zurückliefern, und der Handler wird nicht mehr ausgeführt.
Wird der Event allerdings kurz davor getriggert, dann hält der shared_ptr in der Handler-Wrapper Funktion das Objekt am Leben, bis der Event fertig ausgefürht wurde. -> Problem gelöst
-
ipsec schrieb:
Nexus schrieb:
Ich habe zuerst an
boost::signalund dessen Combiner gedacht, allerdings müsste dafür der Rückgabetyp immer vom gleichen Typ sein.Nach meinem Verständnis fordert nop den Rückgabewert
void.In meinen Fall ganz klar: ja.
Theoretisch könnte man NOP zu "if expired return default" umdeuten, wenn man NOP sehr weit auslegt.Wobei...
Ich hab' mir die Doku der Boost.signals und Boost.signals2 angesehen. So wie ich das verstehe können die das Problem beide nicht alleine lösen, also nicht ohne dass man wieder selbst dafür sorgt dass es geht.Mal ganz davon abgesehen dass die Boost.signals schonmal sowieso nicht threadsafe ist, und die signals2 nicht in Frage kommt, da sie in der 1.38er Boost noch nicht drinnen ist/war.
-
[quote="hustbaer"]
m_eventConnection = EinEvent.Connect(shared_from_this(), &EinDing::DerHandlerDesDings);Was macht denn diese Zeile?
Sie fügt doch einen Listener zu dem Event hinzu, oder?
Wo werden Sie denn gespeichert? In irgendeiner Liste oder Ähnlichem
oder habe ich das Event-System noch nicht verstanden?
-
Wenn du sowas eh immer wieder brauchst, könnte sich nicht eine etwas aufwändigere, generische Lösung lohnen? Ist natürlich blöd, dass C++98 keine variablen typsicheren Argumentlisten hat, und du daher das Rad neu erfinden musst. Aber im Endeffekt lohnt es sich vielleicht mehr, diese Funktionalität selbst zu implementieren, als auf Biegen und Brechen Code wiederzuverwenden, mit dem du dann doch Einschränkungen hast.
Wenn ich das richtig sehe, müsstest du neben der Weak-Reference-Logik vor allem mehrere Überladungen von
operator()schreiben. Aber das sollte ja nicht unheimlich komplex oder so sein, oder übersehe ich etwas? Falls du Boost.Preprocessor kennst, könntest du damit sogar Codeduplizierung stark einschränken.
-
Speicherleck? schrieb:
hustbaer schrieb:
m_eventConnection = EinEvent.Connect(shared_from_this(), &EinDing::DerHandlerDesDings);Was macht denn diese Zeile?
Sie fügt doch einen Listener zu dem Event hinzu, oder?
Wo werden Sie denn gespeichert? In irgendeiner Liste oder Ähnlichem
oder habe ich das Event-System noch nicht verstanden?Es gibt nicht "das" Event-System, aber wenn du dich darauf beziehst was ich für dieses Project implementiert habe...
Vorweg:
Event.Connectgibt bei mir einenshared_ptr<EventConnection>zurück, nicht direkt einEventConnectionObjekt. Mein Beispiel war also in dem Punkt vereinfacht.Event.Connecterstellt ein neuesEventConnectionObjekt, welches eine Kopie des übergebenen Handlers (Funktors) speichert. Ownership übernimmt dabei sofort einshared_ptr, und zwar einer mit einem speziellen Deleter. Dann wir einweak_ptrauf dasEventConnectionObjekt in einer internen Liste des Events eingetragen.
Dershared_ptrwird schliesslich an den Aufrufer zurückgegeben.Wenn der Aufrufer die Connection auflösen möchte, gibt er einfach den
shared_ptrfrei. Der Deleter sorgt dabei dafür dass dasEventConnectionObjekt wieder aus der internen Liste des Events ausgetragen wird, bevor es gelöscht wird.Und bevor der Handler eines
EventConnectionausgefürht wird, wird derweak_ptrerstmal gelockt. Der so erhalteneshared_ptrwird dann gehalten bis der Handler fertig ausgeführt wurde.Im Prinzip alles sehr ähnlich dem was Boost.Signals2 macht.