initialization in C++03



  • klassenmethode schrieb:

    Mir gefallen die delete-Ketten in den Handlern nicht;

    Mir gefallen die ganzen try/catch Blöcke nicht.

    klassenmethode schrieb:

    Gibt es bessere Alternativen [...]

    Ja, Smartpointer

    klassenmethode schrieb:

    Randbedingungen:
    [...] keine smart pointers [...]

    Mit Verlaub, aber das sind ziemlich bescheuerte Bedingungen, darf man fragen, woher die genau kommen? Wieso überhaupt C++ verwenden?



  • @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?

    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)


  • Mod

    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...


  • Mod

    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;
    };
    

  • Mod

    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::A und B::B den C -Zeiger nicht gleich verwenden (=kein vollständig initialisiertes C -Objekt benötigen), dann die Lösung ohne Zeiger

    C::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 C in zwei Klassen splitten: CBase und C , wobei C von CBase abgeleitet ist.
    A und B sind dann Member von C , bekommen aber nur einen CBase -Zeiger. Zu dem Zeitpunkt wo A und B konstruiert werden ist CBase bereits vollständig initialisiert, A und B kö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 einfach const .

    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 => A bzw. C* => A zu erlauben. Also sollte der Ctor explicit sein.

    Also

    class C;
    class A { 
      public:
        explicit A(C* parent);
      private:
        C* const C_;
    };
    

Anmelden zum Antworten