Frage zur Initialisierungsliste



  • color::color(void)
    {
        this->color(.0f);
    }
    

    Ich glaube mal gelesen zu haben, der this-Pointer dürfe im Konstruktor nicht verwendet werden. Ist das nicht (mehr) so? Ich vermeide das auf jeden Fall.

    Sorry für OT.



  • ghorst schrieb:

    @Skym0sh0 das fliegt dir ganz brav um die ohren, weil sich kontruktoren nicht gegenseitig aufrufen dürfen...

    Kleine Anmerkung: In C++0x soll das gehen, allerdings soll IIRC dadurch keine Reukrsion erlaubt sein.

    EDIT:

    plaintext schrieb:

    Ich glaube mal gelesen zu haben, der this-Pointer dürfe im Konstruktor nicht verwendet werden. Ist das nicht (mehr) so?

    Das war noch nie verboten, man darf/sollte nur keine virtuellen Methoden im Konstruktor aufrufen, da erst nach der Abarbeitung des Konstruktor das Objekt den richtigen Typ hat (dadurch kann man es zum Beispiel schaffen pure virtual Methoden aufzurufen 😃 )

    Felix



  • ja, die erweiterung vondsich gegenseitig aufrufenden ctors ist für c-0x vorgesehen. nur das haben wir noch nicht. 😉

    eine abstract virtuall sollte damit nicht aufrufbar sein, da sollte der compiler meckern. wenn man aber eine instanierte version hat, ist s nicht ratsam, weil es sich nicht so verhält, wie man es erwartet:

    class color{
    public:
      color()
        :red(0),blue(0)
      {
        green=value();
      }
      virtual int value() {return 0;}
    private:
      float red,green,blue;
    };
    
    class color2:public color{
      public:
        virtual int value() {return 1;}
    };
    

    wenn jetzt color2 instanier wird, wird green trotzdem 0 sein, da zum zeitpunkt des setzens nur die vtable von color existiert, erst nach dem verlassen des ctor wird die tabelle auf color2 abgeändert.



  • ghorst schrieb:

    eine abstract virtuall sollte damit nicht aufrufbar sein, da sollte der compiler meckern.

    Also das hier kompiliert unter Comeau und g++ einwandfrei durch (EDIT: und ruft erstaunlicherweise sogar das richtige auf...):

    #include <iostream>
    
    using namespace std;
    
    class Base
    {
        public:
            virtual void Test() = 0;
    };
    
    class Derived : public Base
    {
        public:
            Derived()
            {
                 Test();
            }
            virtual void Test()
            {
                 cout << "Test" << endl;
            }
    };
    

    😃

    Felix

    EDIT: Jetzt habe ich mein richtiges Beispiel wieder gefunden:

    test.h

    class Base
    {
        public:
            Base();
            virtual int Test() = 0;
        private:
            int foo;
            void init();
    };
    
    class Derived : public Base
    {
        public:
            Derived();
            virtual int Test();
    };
    

    Test.cpp

    #include <iostream>
    
    #include "test.h"
    
    using namespace std;
    
    Base::Base()
    {
        init();
    }
    
    void Base::init()
    {
        foo = Test();
    }
    
    Derived::Derived() : Base()
    {
    }
    
    int Derived::Test()
    {
         cout << "Test" << endl;
         return 5;
    }
    
    int main()
    {
        Base *test = new Derived();
        delete test;
        return 0;
    }
    

    Gibt:

    pure virtual method called
    terminate called without an active exception
    


  • @ghorst! Soll ich Dir zu Weihnachten eine Shift-Taste schenken? 🙄



  • ghorst schrieb:

    [...]
    wenn jetzt color2 instanier wird, wird green trotzdem 0 sein, da zum zeitpunkt des setzens nur die vtable von color existiert, erst nach dem verlassen des ctor wird die tabelle auf color2 abgeändert.

    Nein, im ctor wird gar nichts über irgendwelche polymorphen Geschichten gelöst, es wird ganz einfach statisch auf diese Methode zugegriffen wie auf jeden nicht-virtuelle Methode auch. Kurz: ob virtual oder nicht hat im Konstruktor keine Bedeutung, es ist also äquivalent zu color::value() (welches ebenfalls virtual außerhalb des Konstruktors deaktiviert).



  • Phoemuex schrieb:

    ghorst schrieb:

    eine abstract virtuall sollte damit nicht aufrufbar sein, da sollte der compiler meckern.

    Also das hier kompiliert unter Comeau und g++ einwandfrei durch (EDIT: und ruft erstaunlicherweise sogar das richtige auf...):

    #include <iostream>
    
    using namespace std;
    
    class Base
    {
        public:
            virtual void Test() = 0;
    };
    
    class Derived : public Base
    {
        public:
            Derived()
            {
                 Test();
            }
            virtual void Test()
            {
                 cout << "Test" << endl;
            }
    };
    

    Das ist auch nciht weiter verwunderlich, da du nirgendwo mit Polymorphie arbeitest. Du rufst im Ctor von Derived Test() auf und der Compiler macht da zur Compilezeit einen Aufruf von Derived::Test draus.
    Genausowenig verwunderlich ist, dass du einen Compilerfehler bekommst wenn du im Ctor von Test eine pur virtuelle Funktion direkt aufrufst.



  • FALSCH! schrieb:

    Nein, im ctor wird gar nichts über irgendwelche polymorphen Geschichten gelöst, es wird ganz einfach statisch auf diese Methode zugegriffen wie auf jeden nicht-virtuelle Methode auch.

    und das kann er aus einem einfachen grund: er kennt die exakte vtable, nämlich seine... da er die kennt, können die virtual functions entsprechend auf "normale" reduziert werden. muss er aber nicht machen. wenn er lustig ist, kann er die gerne auch über das langsamere virtual-interface aufrufen.

    @pumuckle das "pure virtual method called
    terminate called without an active exception" ist kein compilerfehler, sondern ein laufzeitfehler. (falls du den meintest.)

    @Phoemuex ok. der klappt tatsächlich, weil der compiler nicht erkennt, dass init eigentlich eine verkappte virtuelle funktion ist. das ist übrigens das, was ich oben mit der vtable zu erklären versuchte.



  • ghorst schrieb:

    FALSCH! schrieb:

    Nein, im ctor wird gar nichts über irgendwelche polymorphen Geschichten gelöst, es wird ganz einfach statisch auf diese Methode zugegriffen wie auf jeden nicht-virtuelle Methode auch.

    und das kann er aus einem einfachen grund: er kennt die exakte vtable, nämlich seine... da er die kennt, können die virtual functions entsprechend auf "normale" reduziert werden. muss er aber nicht machen. wenn er lustig ist, kann er die gerne auch über das langsamere virtual-interface aufrufen.

    Nein, das stimmt so einfach nicht. Er macht es so wie ich gesagt habe, im Standard gibt es keine vtables. Es ist nur ein Seiteneffekt in der Praxis, da die Compiler mit vtables arbeiten, aber der Standard sagt ganz eindeutig das was ich gesagt habe. Und darauf musst du dich als Programmierer verlassen nicht auf die Implementierung von den Compilern, welche oftmals sogar closed-source sind.



  • FALSCH! schrieb:

    ...der Standard sagt ganz eindeutig das was ich gesagt habe....

    Magst Du mal eine Quellenangabe liefern ? Ich bin relativ sicher, dass der Std eine Technik wie vtables weder vorschreibt noch verbietet ... aber verbietet er wirklich "Polymorphie im Konstruktor" ?

    Gruß,

    Simon2.



  • die erklärungen stehen in "12.7 Construction and destruction [class.cdtor]" (da geht es um polymorphie im ctor und dtor)
    und "12.6.2 - Initializing bases and members [class.base.init]" und dort der 8. abschnitt.

    was ich hier schrieb, ist das, was ich daraus gefolgert habe.



  • Simon2 schrieb:

    aber verbietet er wirklich "Polymorphie im Konstruktor" ?

    Wenn er es nicht direkt verbietet, dann doch implizit, da er verlangt, dass Basisklassenteile eines Objektes immer zuerst initialisiert werden. Und während der Konstruktion des Basis-Objektes hat das Objekt keine Möglichkeit festzustellen, dass es eigentlich ein abgeleitetes Objekt ist, genauer gesagt: werden soll. Nach der Philosophie von C++ existiert ein Objekt erst nach dem Abarbeiten des Ctors und kann auch dann erst polymorphes verhalten zweigen. Genausowenig ist Polymorphie im Dtor möglich, da der abgeleitete Teil eines Objektes bereits nicht mehr existent ist, wenn der Basisklassen-Dtor betreten wird.

    Links zum Thema:
    http://www.artima.com/cppsource/nevercall.html
    http://gcc.gnu.org/ml/gcc-help/2001-11/msg00208.html (und followups)
    http://www.gamedev.net/community/forums/topic.asp?topic_id=483196



  • naja. ich würde es so ausdrücken, der standard drückt sich. aus 12.7 - 2:

    Member functions, including virtual functions (class.virtual), can be called during construction or destruction (class.base.init). When a virtual function is called directly or indirectly from a constructor (including from the mem-initializer for a data member) or from a destructor, and the object to which the call applies is the object under construction or destruction, the function called is the one defined in the constructor or destructor's own class or in one of its bases, but not a function overriding it in a class derived from the constructor or destructor's class, or overriding it in one of the other base classes of the most derived object (intro.object).

    ich verstehe das so: virtuell geht schon, wird aber immer in dem context der klasse ausgeführt, zu der der ctor gehört. daraus folgt: kann wegoptimiert werden, wenn der compiler es erkennt. bei indirekten kann er es nicht unbedingt erkennen und er läuft in die falle.



  • ghorst schrieb:

    ich verstehe das so: virtuell geht schon, wird aber immer in dem context der klasse ausgeführt, zu der der ctor gehört.

    Du kannst virtuelle Funktionen aufrufen, das ja. Du wirst aber kein Polymorphes Verhalten bekommen, sprich es werden NICHT die abgeleiteten Versionen aufgerufen, wenn du die Fuktion im Basisklassen-Ctor aufrufst.

    daraus folgt: kann wegoptimiert werden, wenn der compiler es erkennt. bei indirekten kann er es nicht unbedingt erkennen und er läuft in die falle.

    Da wie du schon geschrieben hast die Funktion immer im Kontext der Klasse aufgerufen wird, kann der Compiler auf jeden Fall schon zur Compilezeit erkennen, welche Version aufgerufen wird. Das Verhalten ist in dem Fall unabhängig davon, ob die Funktion virtuell ist oder nicht:

    class Base {
    protected:
      virtual void virt() {cout << "Base::virt\n";}
      void nonvirt() {cout << "Base::nonvirt\n";}
    public:
      Base() {}
    };
    
    class Intermediate1 : public Base {
    public:
      Intermediate1()
      {
        virt();
        nonvirt();
      }
    };
    
    class Intermediate2 : public Base {
    protected:
      virtual void virt() {cout << "Inter2::virt\n";}
      void nonvirt() {cout << "Inter2::nonvirt\n";}
    public:
      Intermediate2()
      {
        virt();
        nonvirt();
      }
    };
    
    class Derived1 : public Intermediate1
    {
    protected:
      virtual void virt() {cout << "Derived1::virt\n";}
    };
    
    class Derived2 : public Intermediate2
    {
    protected:
      virtual void virt() {cout << "Derived2::virt\n";}
    };
    
    int main()
    {
      cout << "D1:\n";
      Derived1 d1;
      cout << "\nD2:\n";
      Derived2 d2;
    }
    

    Output:

    D1:
    Base::virt
    Base::nonvirt
    
    D2:
    Inter2::virt
    Inter2::nonvirt
    

    Es werden die virtuellen und nicht virtuellen Methoden aufgerufen die den Intermediate-Klassen bekannt sind, da während deren Konstruktion eben nicht bekannt ist, dass das Objekt mal ein Derived werden soll.



  • bei den indirekten ging es mir um das bsp von Phoemuex, der wunderbar vorgemacht hat, dass der gcc zu blöd ist, rauszubekommen, dass er ein ungültige funktion aufruft. man könnte es im compiler so implementieren, dass er das auch erkennt.
    die frage ist dann nur, stehen kosten und nutzen in einem sinnvollen verhältnis? und bis zu welcher tiefe sollte er nicht-virtuelle funktionen nach virtuellen durchsuche, um diese auf abstract virtual zu testen bzw. durch normale calls ersetzen?
    aus den beiden fragen heraus, werden sich wohl einige compiler wie der gcc verhalten und einfach virtual-aufrufe durchführen, auch wenn sie eigentlich diese durch normale ersetzen könnten. hier greift dann der standard, der hier die normalen polysmorphieregeln zur auflösung vorschreibt, wobei die klasse des aktuelle ctors als die letzte in der hierachie angesehen wird.



  • Aha !!! 💡 💡

    Danke - man lernt nie aus,

    Simon2.


Anmelden zum Antworten