initialization in C++03



  • tntnet schrieb:

    Warum keine Smart Pointer? Oder warum überhaupt Pointer?

    keine Smartpointer und legacy C++ ist Bedingung. Sonst wäre es ja einfach. Und warum überhaupt Pointer? Weil der ctor von A, B einen parent-Zeiger braucht:

    A::A(C* parent) : C_(parent){ ... }
    B::B(C* parent) : C_(parent) { ... }

    damit fällt in-class init. a la "class C { ... private: A a_; ...}" schon mal weg.



  • Geht doch mit Initializer Lists:

    class C
    {
    public:
      C()
       : a_(this), B_1(this), B_2(this)
      {
      }
    private:
      A a_;
      B B_1;
      B B_2;
    };
    

    Oder brauchen die Konstruktoren von A oder B schon ein vollständiges C Objekt?

    klassenmethode schrieb:

    keine Smartpointer und legacy C++ ist Bedingung

    Legacy C++ kann ich ja noch verstehen. Ist das irgendeine Programmieraufgabe oder wieso sind Smart Pointer ausgeschlossen? Man kann sich ja auch in C++03 einfach seinen unique_ptr Klon ohne move und andere tolle Tricks schnell selbst bauen. Oder was aus boost nehmen.



  • sebi707 schrieb:

    Oder brauchen die Konstruktoren von A oder B schon ein vollständiges C Objekt?

    Wäre auch kein Hinderungsgrund. Wenn A oder B deklariert wird, reicht eine forward deklaration von C. Wird der Konstruktor implementiert, dann kann bereits C kennen

    class C;  // forward deklaration
    class A
    {
    public:
        explicit A(const C& c);  // hier braucht er definitiv noch kein C
    };
    
    class C
    {
    public:
        C()
            : a_(*this)
        { }
    private:
        A a_;
    };
    
    A::A(const C& c)
    {
      // und hier kennt er das vollständige C Objekt
    }
    

    Zeilen 1-7 gehören normalerweise in "A.h", 8-17 in "C.cpp" und 18-21 in "A.cpp"

    Ach - C-Objekt klingt komisch. Ist doch ein C++-Objekt 😉 . "Objekt vom Typ C" wäre besser. Aber was solls.



  • tntnet schrieb:

    Wäre auch kein Hinderungsgrund. Wenn A oder B deklariert wird, reicht eine forward deklaration von C.

    Sorry, ich meinte vollständig im Sinne von vollständig konstruiert. Wenn der Konstruktor von A eine bereits erfolgte und möglicherweise komplizierte Initialisierung des C Objekts voraussetzt dann möchte man das A Objekt vielleicht erst am Ende des Konstruktors von C aufrufen. Das ist dann nur schwierig mit Initializer Lists zu realisieren.



  • initializer list geht nicht, weil wie schon oben geschrieben:

    class A{ ... };   // ctor might throw
    class B{ ... };   // ctor might throw
    

    Daß der ctor von A hingegen ein vollständig initialized C-Objekt benötigt, ist nicht verlangt. Es reicht die Adresse.



  • Und? Lass die doch ne Exception werfen. Die Member werden schon richtig aufgeräumt.



  • 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