initialization in C++03
-
klassenmethode schrieb:
@sebi707: kannst du mal zeigen, wie du eine exception aus einer initializer list fängst, die mit
C::C() : A_(new A(this)), B_1(new B(this)), B_2(new B(this)) {}gebaut ist?
Das ist nicht, was sebi meint, es sei denn A_, B_1 (und B_2) sind Smartpointer. Falls es Smartpointer sind, gibt es keinen Grund zu fangen.
klassenmethode schrieb:
C::C() : A_(A(this)), B_1(B(this)), B_2(B(this)) {}will ich ja nicht, weil A und B keinen copy-ctor haben (wäre zu aufwendig)
dito. Niemand verlangt, dass kopiert werden soll.
C::C() : A_(this), B_1(this), B_2(this) {}ist gut genug.
Wenn neben dem Aufräumen noch etwas anderes im Handler getan werden muss: Stichwort function-try-block
-
klassenmethode schrieb:
@sebi707: kannst du mal zeigen, wie du eine exception aus einer initializer list fängst, die mit
C::C() : A_(new A(this)), B_1(new B(this)), B_2(new B(this)) {}gebaut ist?
Mit einem function-try-block. Das hilft dir aber leider nicht viel, weil du nicht weißt, wo genau die exception flog und welche Member nun aufgeräumt werden müssen. Die einzige, mir bekannte, ordentliche Lösung für all diese Probleme, ist RAII, aber das darf ja aus irgendeinem Grund offenbar nicht verwendet werden...
-
Das hilft dir aber leider nicht viel, weil du nicht weißt, wo genau die exception flog und welche Member nun aufgeräumt werden müssen.
Im function-try-block wären alle Member schon zerstört.
-
Arcoth schrieb:
Das hilft dir aber leider nicht viel, weil du nicht weißt, wo genau die exception flog und welche Member nun aufgeräumt werden müssen.
Im function-try-block wären alle Member schon zerstört.
Stimmt. Der Punkt ist, dass alles per new allokierte Memory hier im Falle einer Exception zwangsweise leaked, so lange man keine Smartpointer einsetzt...

-
Unabhängig davon, dass ich die Lösung ohne Zeiger auch am besten finde:
Der Originalcode lässt sich mit relativ einfachen Mitteln etwas aufhübschen.class C{ public: C() : A_(0), B_1(0), B_2(0) { try{ A_=new A(); B_1=new B(); B_2=new B(); } catch(...){ dealloc(); throw; } } private: void dealloc() { delete A_; delete B_1; delete B_2; } ... A* A_; B* B_1; B* B_2; };
-
dot schrieb:
Arcoth schrieb:
Das hilft dir aber leider nicht viel, weil du nicht weißt, wo genau die exception flog und welche Member nun aufgeräumt werden müssen.
Im function-try-block wären alle Member schon zerstört.
Stimmt. Der Punkt ist, dass alles per new allokierte Memory hier im Falle einer Exception zwangsweise leaked, so lange man keine Smartpointer einsetzt...

Du hast ihn womöglich dazu angeregt, nachzudenken, wie er diese wohl im function-try-block zerstören sollte. Ich wollte nur sicherstellen, dass er diese Idee nicht verfolgt.
Eigentlich sollte ein Artikel zu RAII rauskommen, der wirklich bitter nötig scheint, aber jemand hatte ja über die Feiertage keine Zeit…

-
Wenn
A::AundB::BdenC-Zeiger nicht gleich verwenden (=kein vollständig initialisiertesC-Objekt benötigen), dann die Lösung ohne ZeigerC::C() : A_(this), B_1(this), B_2(this) {}Ansonsten Smart-Pointer.
Oder wenigstens eine einfache, kleine Guard-Klasse:template <class T> struct DeleteGuard { DeleteGuard() : p(0) {} ~DeleteGuard() { delete p; } T* p; private: DeleteGuard(DeleteGuard const&); DeleteGuard& operator = (DeleteGuard const&); }; class C { public: C() { A_.p = new ...; B_1.p = new ...; B_2.p = new ...; } private: DeleteGuard<A> A_; DeleteGuard<B> B_1; DeleteGuard<B> B_2; };Oder
Cin zwei Klassen splitten:CBaseundC, wobeiCvonCBaseabgeleitet ist.
AundBsind dann Member vonC, bekommen aber nur einenCBase-Zeiger. Zu dem Zeitpunkt woAundBkonstruiert werden ist CBase bereits vollständig initialisiert,AundBkönnen also problemlos darauf zugreifen.
-
ich habe noch mal drüber nachgedacht - was Sebi707 vorgeschlagen hat, scheint mir die beste Lösung
class C; class A { public: A(C &parent); virtual ~A(); private: C &C_; }; class B { public: B(C &parent); virtual ~B(); private: C &C_; }; class C { public: C(); virtual ~C(); private: A A_; B B1_; B B2_; }; C::C() : A_(*this), B1_(*this), B2_(*this){} C::~C(){} A::A(C &parent) : C_(parent){} A::~A(){} B::B(C &parent) : C_(parent){ throw exception(); } B::~B(){} int main(void){ try { C c; } catch(...){ /* ... */ } }hübsch - die delete-Ketten sind weg.
-
Wieso die ganzen virtuellen Destruktoren? Ich finds auch etwas merkwürdig, dass diese Klassen alle eine Referenz auf das Objekt, dessen Teil sie sind, brauchen...
-
klassenmethode schrieb:
i
class C; class A { public: A(C &parent); virtual ~A(); private: C &C_; }; (...)Also hier würde ich wirklich keine Referenz verwenden, sondern einen Zeiger.
Wenn du das Ändern des Zeigers verhindern willst, dann mach die Membervariable einfachconst.Und der virtuelle Dtor ist mMn. auch unnütz. Kann mir grad keine Situation vorstellen wo man den brauchen könnte. Bzw. schlimmer als unnütz: er kommuniziert etwas, was so nicht stimmt. Nämlich dass man sinnvoll von A ableiten kann.
Und dann macht es wohl auch keinen Sinn eine implizite Konvertierung
C=>Abzw.C*=>Azu erlauben. Also sollte der Ctorexplicitsein.Also
class C; class A { public: explicit A(C* parent); private: C* const C_; };