Zu meinen Lebzeiten....
-
Grüße, Erdlinge!
In meiner Funktion als Programmierer fühlte ich mich genötigt, für eine Graph-Darstellung ein kleines Widgetsystem und damit das Rad neu zu erfinden. Mit dem Event-Routing für Benutzereingaben bin ich recht zufrieden, allerdings stört mich seit Beginn das Problem mit den Lebenszeiten der Fenster-Objekte im Bezug zum Widget-Manager-Objekt (auch wenn eigentlich alles erstmal funktioniert, aber das kann ich so nicht stehen lassen), mal abgesehen von einigen Hässlichkeiten mit Bezug auf die Pimpl-Umsetzung.
Derzeit sieht das ganze folgendermaßen aus:class WidgetManager; class Widget { public: Widget( WidgetManager& m, Widget* parent = 0 ) : mgr_(&m) { mgr_->NewWidget( this ); if( parent ) parent->AddChild( this ); } private: WidgetManager* mgr_; }; class SomeWidget : public Widget { /* .... */ }; class OtherWidget : public Widget { /* .... */ }; class SomeOsWindow : IWidgetContainer { public: SomeOsWindow() : mgr_( this ) /* das ist schon unsauber */, desktopWidget_( mgr_ ) { pWidget_ = new Widget( mgr_, /* parent = */ &desktopWidget_ ); } ~SomeOsWindow() { delete pWidget_; } private: WidgetManager mgr_; SomeWidget desktopWidget_; OtherWidget* pWidget_; };Wie man sieht, halten die Widgets einen Zeiger auf den entsprechenden Manager, in dessen Kontext sie leben. Allerdings hindert sie eigentlich auch niemand, den Manager zu überleben und das ist gerade mein Problem.
Zuerst habe ich mir überlegt, dass ich das irgendwie lösen könnte, indem das gepimpl'te WidgetImpl immer vom Manager erstellt wird und dann als ein solcher Zeiger zurückgegeben wird, der zwar die Dereferenzierung zulässt, aber ansonsten vom Manager genullt werden kann. Sozusagen ein weak_ptr, der aber nicht zu einem shared_ptr konvertiert werden kann, sondern nur dereferenziert werden kann, falls das Objekt noch lebt. Irgendwie so:
class SomeOsWindow : IWidgetContainer { public: SomeOsWindow() : mgr_( this ) /* das ist schon unsauber */, desktopWidget_( mgr_ ) { desktopWidget_ = mgr_.MakeWidget(); pWidget_ = new OtherWidget(); *pWidget_ = mgr_.MageWidget(); pWidget_->SetParent( &desktopWidget_ ); } ~SomeOsWindow() { delete pWidget_; } private: WidgetManager mgr_; SomeWidget desktopWidget_; OtherWidget* pWidget_; };Nun könnte die Widget-Implementierung immer testen, ob die WidgetImpl-Implementierung (gleichbedeutend mitsamt des Managers) noch lebt, wenn sie darauf zugreifen muss. Allerdings ist es halt für die Widgets immernoch ziemlich zufällig, wann ihnen der Manager unter dem Hintern weggenommen wird.
Als ich heute morgen so im Bett rumlag, kam mir die Idee, dass, wenn man die beiden Dinge Widget und WidgetManager für den Benutzer der Bibliothek trennt, man immer diese Probleme bekommt. Also wäre die Lösung eventuell, dass der Benutzer gar keinen WidgetManager halten sollte, sondern die Widgets das für ihn übernehmen und zwar in einer solchen Form, dass sie einen erstellen, wenn sie einen brauchen. Nach dem (vereinfachten) Motto:
class Widget { public: Widget( Widget* parent ) { if( parent ) { pMgr_ = parent->GetManager(); parent->AddChild( this ); } } intrusive_ptr<WidgetMgr> GetManager() { return ( pMgr_ ) ? pMgr_ : pMgr_ = new WidgetMgr; } private: intrusive_ptr<WidgetMgr> pMgr_; }; class SomeOsWindow : IWidgetContainer { public: SomeOsWindow() { pMgr_ = desktopWidget_.GetManager(); pMgr_->SetContainer( this ); pWidget_ = new OtherWidget(); pWidget_->SetParent( &desktopWidget_ ); } ~SomeOsWindow() { delete pWidget_; pMgr_->SetContainer( 0 ); } void RedrawRect( SomeOsRedrawObject& obj, Rect& r ) { pMgr->RedrawRect( obj, r ); } private: intrusive_ptr<WidgetManager> pMgr_; SomeWidget desktopWidget_; OtherWidget* pWidget_; };Die Widgets würden halt immer aufpassen, dass alle Widgets innerhalb einer Hierarchie denselben Manager benutzen, aber es wird auch erst einer erstellt, wenn er denn wirklich gebraucht wird, sodass nicht immer gleich ein temporärer erstellt wird, wenn man gerade ein Widget außerhalb einer Hierarchie erstellt und anschließend einer hinzufügt. Damit hätte sich meiner Meinung nach das Lebenszeit-Problem gelöst. Allerdings erkaufe ich mir das mit einem anderen Problem. Und zwar braucht der WidgetManager eigentlich die Hilfe von der ihn benutzenden Klasse, da wenn Widgets sich gerne neu zeichnen lassen würden, er die Koordinaten umrechnet und dann der benutzenden Klasse mitteilt, welcher/e Ausschnitt/e neu gezeichnet werden muss/müssen. Das heißt es liegt wieder an dem Benutzer, dass er daran denkt, entsprechend diesen Zeiger per SetContainer zu setzen und bei der Zerstörung wieder zurückzusetzen (SetContainer in dem obigen Beispiel), während bei der ersten Lösung ja der Konstruktor von WidgetManger verhindern könnte, das Objekt zu erstellen, ohne dass man einen entsprechenden Zeiger mitgibt.
Was haltet ihr davon? Gibts irgendwie eine wesentlich bessere Lösung? Bin ich da irgendwie auf dem Holzweg?
Ihr seht, ich bin da irgendwie noch etwas verwirrt^^Viele Grüße,
Deci
-
*vorsichtignachobenstupsundmoderatorenliebanschauundnixböseswill*
Mein Problem ist eventuell, dass ich zu verwirrt bin um die Verwirrung zu erkennen, die mein verwirrender Text beim Leser erzeugt. Falls etwas unklar ist, so möchte ich das gerne klarer darstellen. Für virtuellen Kaffee und Kuchen kann ich auch sorgen! :xmas1:
-
Hrmmm, ich glaube ich habe eine Lösung zu letztgenanntem Problem gefunden. Und zwar muss zum ordnungsgemäßen Darstellen einer Hierarchie das Wurzel-Widget sozusagen per SetParent einem IWidgetContainer als Kind zugewiesen werden. desktopWidget_->SetParent( this ); sozusagen - eine extra Überladung für SetParent. Erst in diesem Moment wird für die Hierarchie ein WidgetManager erstellt. IWidgetContainer sorgt dann in seinem Destruktor von alleine dafür, dass die Widgets ihren Manager verlieren sobald das Container-Objekt (Also meinetwegen ein echtes Betriebssystem-Fenster) zerstört wird. Klingt das gut?^^
Viele Grüße,
Deci
-
Na siehste..
Manchmal muß man nur drüber reden, um die Lösung zu finden.
Gruß,
DC
-
Hehehe, das erinnert mich irgendwie gerade an dieses klassische Bild von jemandem, der dahergelaufen kommt, einen Monolog hält und sich - noch bevor jemand der erstaunten Zuhörer was antworten kann - für das Gespräch bedankt und faselnd von dannen zieht^^
Fröhliche Weihnachten! :xmas1:
-
So, nachdem ich erstmal anderweitig zu tun hatte, bin ich jetzt dabei, das entsprechend meiner letzten Aussagen umzubasteln.
Wie es ja immer so ist, liegt der Teufel im Detail. Die Widgets leben ja nun natürlicherweise in einer gewissen Hierarchie. Oftmals erzeugt man eine dynamische Anzahl an Widgets etc.. Da wäre ja die nutzung von tr1::shared_ptr (mindestens von "außerhalb der Bibliothek") eine flexible Sache. Nun ist für mich aber die Frage, ob ich das konsequent innerhalb der Bibliothek weiterführen sollte. Wenn ich das nicht tue, hätte ich überall im Client-Code Dinge wieclass WidgetA : public Widget; class WidgetB : public Widget; widget1 = std::make_shared<WidgetA>(...); widget2 = std::make_shared<WidgetB>(...); widget2->SetParent( widget1.get() ); // <----stehen. Zudem würde ich innerhalb der Objekte (Also in der Basisklasse Widget) auf "komplizierte" Weise die Lebenszeit der Väter/Kinder überwachen (so wie ich es gerade schon tue), weil die ja nicht mitbekommen, wenn die anderen verschwinden.
Das ganze würde sich sehr vereinfachen, wenn Widget intern auch auf shared_ptr und weak_ptr setzt. Allerdings können Widgets dann keine echten Member mehr sein oder auf dem Stack erstellt werden (letzteres wäre nicht so das Problem^^).
Würdet ihr sagen, dass es "schlechtes Design" ist, wenn die Bibliothek es so in Stein meißelt, dass die Dinger dynamisch erstellt werden müssen?
Danke schon im Voraus für eure Weisheit!
DeciEdit: Und überhaupt... was macht eigentlich am meisten Sinn? SetParent? AddChild? Gleich beides?^^ Das habe ich mich ja auch schon öfter mal gefragt

Edit2: Oder gar subordinate( widget1, widget2 );? Ein überladener Operator wäre auch noch so eine Möglichkeit.