Kind-Funktion



  • Hallo,
    man hat zwei Klassen:

    class A
    {
    public:
      A();
      void foo() {/* tue dies*/}
    };
    
    A::A() {foo();}
    
    class B : public A
    {
    public:
      B();
      void foo() {/* tue jenes*/}
    };
    
    B::B() : A() {}
    

    Wenn ich nun ein B-Objekt erstelle, wird A::foo() aufgerufen. Gibt es eine Möglichkeit, dass, wenn ich ein Objekt der Kind-Klasse erstelle, der Konstruktor auch dessen Funktion aufruft?



  • In der Hoffnung, dich richtig verstanden zu haben: das gewünschte Late Binding erreichst du in C++ über das Schlüsselwort "virtual"

    also

    virtual void foo();
    


  • Das dachte ich auch, aber virtual ändert leider nichts.
    Wenn es eigentlich so sein sollte, dann liegt das Problem wohl an meinem spezifischen Code.


  • Mod

    Gibt es eine Möglichkeit, dass, wenn ich ein Objekt der Kind-Klasse erstelle, der Konstruktor auch dessen Funktion aufruft?

    B::B() : A() { foo(); }
    

    Vermutlich wolltest du eine andere Frage stellen.



  • Wenn ich das mache, wird sowohl A::foo() als auch B::foo() aufgerufen. Das geht aber nicht, da A::foo() in einem B-Objekt zum Laufzeitfehler führt.



  • zwutz schrieb:

    In der Hoffnung, dich richtig verstanden zu haben: das gewünschte Late Binding erreichst du in C++ über das Schlüsselwort "virtual"

    also

    virtual void foo();
    

    Das funktioniert in regulären Methoden, allerdings nicht im Konstruktor.

    Referenzen dazu:
    http://www.artima.com/cppsource/nevercall.html
    http://msdn.microsoft.com/de-de/library/s8e39b8h(v=vs.90).aspx

    Das, was der TE möchte, ist also so, wie er sich das vorstellt, im Konstruktor nicht möglich.

    Eine Option wäre Compositing (glaube, so nannte man das :p) zu verwenden.
    Beispiel:

    #include <iostream>
    
    template <class Derived>
    class A
    {
    public:
        A() { reinterpret_cast<Derived*>(this)->Derived::foo(); }
        void foo() { std::cout << "A" << std::endl; }
    };
    
    // foo wird "überladen"
    class B : public A<B>
    {
    public:
        B() : A() {}
        void foo() { std::cout << "B" << std::endl; }
    };
    
    // foo wird nicht "überladen"
    class C : public A<C>
    {
    public:
        C() : A() {}
    };
    
    int main()
    {
        B b; // Ausgabe "B"
        C c; // Ausgabe "A"
        return 0;
    }
    

    PS.:
    Für alte Compiler ohne "Empty Base Class Optimization" muss man den reinterpret_cast noch etwas anpassen, damit das 1 Zusatz-Byte mit verrechnet wird.
    Aber insgesamt sollte man hier wohl besser sein Design nochmal überdenken.



  • Auf ein reinterpret_cast, dass, wie du sagst, nicht einmal compilerunabhängig funktioniert, möchte ich gerne verzichten.

    Zum Design fällt mir nichts besseres ein. 😕
    A und B sind Widgets eines GUI-Projektes. B ist das Gleiche wie A, mit dem Unterschied, dass es auch deaktiv sein kann, sodass die meisten Nutzeraktionen nicht möglich sind.
    Man kann sich das in etwa so vorstellen, dass A einen Member Datentyp* hat, der in einem B-Objekt, wenn dieses deaktiv ist, auf nichts zeigt und Funktionsaufrufe somit natürlich einen Fehler hervorrufen.



  • verstehe ich nicht

    wieso kann man auf den nullzeiger nicht testen? warum keine gemeinsame basisklasse für A und B?



  • Wurstinator schrieb:

    A und B sind Widgets eines GUI-Projektes. B ist das Gleiche wie A, mit dem Unterschied, dass es auch deaktiv sein kann, sodass die meisten Nutzeraktionen nicht möglich sind.

    Eventuell so?

    class DeactivatableWidget : public Widget
    {
        Widget *other;
        bool enabled;
    
    public:
        virtual void input()
        {
            if(enabled)
                other->input();
        }
    };
    


  • Spontan faellt mir eine einfach Loesung ein (wenn auch nich unbedingt optimal): Du machst eine Zwei-Schritte Konstruktion: A a; a.create()
    create wuerde dann das korrekte foo aufrufen.

    Aber wozu brauchst du eigentlich foo ?
    foo scheint bei dir eine Funktion zu sein, die in jeder abgeleiteten Klasse implementiert wird, und dann bei der Konstruktion aufgerufen werden soll.
    Es geht auch einfacher: Pack alles, was in foo steht direkt in die Konstruktoren der abgeleiteten Klassen ein.
    Oder hab ich was missverstanden?



  • Wurstinator schrieb:

    Wenn ich das mache, wird sowohl A::foo() als auch B::foo() aufgerufen. Das geht aber nicht, da A::foo() in einem B-Objekt zum Laufzeitfehler führt.

    Verstehe ich das richtig? Es darf immer nur die foo der Klasse aufgerufen werden, von der du gerade eine Instanz erstellst:
    - A <-- B <-- C
    - Erzeugst du eine A-Instanz soll nur A::foo aufgerufen werden.
    - Erezugst du eine B-Instanz soll nur B::foo aufgerufen werden.
    - Erezugst du eine C-Instanz soll nur C::foo aufgerufen werden.

    Also so was?

    #include <iostream>
    using namespace std;
    
    class A
    {
    	public:
    		static A create_new_A()
    		{
    			A a;
    			a.foo();
    			return a;
    		}
    		virtual void foo()
    		{
    			cout << "foo A";
    		}
    };
    
    class B : public A
    {
    	public:
    		static B create_new_B()
    		{
    			B b;
    			b.foo();
    			return b;
    		}
    		virtual void foo()
    		{
    			cout << "foo B";
    		}
    };
    
    class C : public B
    {
    	public:
    		static C create_new_C()
    		{
    			C c;
    			c.foo();
    			return c;
    		}
    		virtual void foo()
    		{
    			cout << "foo C";
    		}
    };
    
    int main()
    {
    	A a = A::create_new_A();
    	B b = B::create_new_B();
    	C c = C::create_new_C();
    }
    

    Wohl ein Designfehler. Das sieht irgendwie grausig aus.



  • Wurstinator schrieb:

    Wenn ich das mache, wird sowohl A::foo() als auch B::foo() aufgerufen. Das geht aber nicht, da A::foo() in einem B-Objekt zum Laufzeitfehler führt.

    Das ist aber ziemlich doof, weil ein B dann nicht als A verwendet werden kann, und Du damit das LSP verletzt.



  • @ trustee: Ich sagte "man kann sich das so vorstellen". Genau sieht es im Programm so aus, dass A einen Verweis auf einen Datentyp hat, der widerrum einen Array-Index enthält. Ist B deaktiviert, kann dieser Array-Index im Datentyp uninitialisiert sein.

    @ ipsec: Hm, ich müsste DeactivatableWidget dann natürlich noch zum friend machen, aber ansonsten gefällt mir der Vorschlag gut. Ich denke nochmal genauer darüber nach, ob damit alles funktioniert, und nutze ihn dann eventuell.

    @ Ferris: Es geht mir nicht nur darum, dass der Code speziell für die abgeleitete Klasse aufgerufen werden soll, sondern auch darum, dass der Code der Elternklasse nicht aufgerufen wird.

    @ Ferris & out: Klar, mit einer mehrbefehligen Konstruktion oder einer statischen Funktion geht es natürlich, aber wie ihr sagt, ist das doof; daher will ich einen anderen Weg finden.

    @ Tachyon: Das lässt sich ja einfach lösen, wenn man so etwas wie das macht:

    void A::foo()
    {
      if (!is_derived())
      {
        // tue dies
      }
    }
    

    Nur eben im Konstruktor funktioniert das nicht.


  • Mod

    Wurstinator schrieb:

    @ Tachyon: Das lässt sich ja einfach lösen, wenn man so etwas wie das macht:

    Sobald du von einer konkreten Klasse ableitest, wird das LSP verletzt - auf irgendwelche Implementationsdetails kommt es gar nicht an. Ich verstehe nicht, wieso nach 20 Jahre Frameworks immer noch nach Prinzipien designed werden, die weder theoretisch noch praktisch wirklich funktionieren.

    Wenn es nur darum geht, eine bestimmte Funktion nicht aus dem Basisklassenkonstruktor heraus aufzurufen: was spricht dagegen, einfach einen eigenen (protected) Konstruktor für diesen Zweck in der Basisklasse zu implementieren?

    Alternativ könnte man auch A durch B implementieren: ein Widget, das grundsätzlich sowohl inaktiv als auch aktiv sein kann, kann ja ohne weiteres auch eines repräsentieren, welches (zufällig) immer aktiv ist. Umgekehrt gilt das aber gerade nicht.
    Oder aber A wird aufgeteilt in einen vererbbaren abstrakten Teil, von dem auch B erben kann, und einen Rest (der sich ggf. im entsprechenden Konstruktor erschöpft).



  • Hm, dann verstehe ich das LSP wohl nicht richtig.

    Wenn es nur darum geht, eine bestimmte Funktion nicht aus dem Basisklassenkonstruktor heraus aufzurufen: was spricht dagegen, einfach einen eigenen (protected) Konstruktor für diesen Zweck in der Basisklasse zu implementieren?

    Wie soll das funktionieren? Ich kann ja nicht einfach einen zweiten Konstruktor mit dem gleichen Funktionsheader erstellen, oder?

    Alternativ könnte man auch A durch B implementieren

    Hm, ist zwar naheliegend, bin ich aber nicht drauf gekommen. Ich werd mir diese Möglichkeit mal durch den Kopf gehen lassen.

    Oder aber A wird aufgeteilt in einen vererbbaren abstrakten Teil, von dem auch B erben kann, und einen Rest (der sich ggf. im entsprechenden Konstruktor erschöpft).

    Versteh ich nicht 😕


  • Mod

    Wurstinator schrieb:

    Hm, dann verstehe ich das LSP wohl nicht richtig.

    Wenn es nur darum geht, eine bestimmte Funktion nicht aus dem Basisklassenkonstruktor heraus aufzurufen: was spricht dagegen, einfach einen eigenen (protected) Konstruktor für diesen Zweck in der Basisklasse zu implementieren?

    Wie soll das funktionieren? Ich kann ja nicht einfach einen zweiten Konstruktor mit dem gleichen Funktionsheader erstellen, oder?

    Niemand zwingt dich, die gleiche Signatur zu verwenden

    struct A
    {
        void foo();
        A() { foo(); }
    protected:
        struct no_foo_tag {};
        A(no_foo_tag) {};
    };
    
    struct B : A
    {
        B() : A(no_foo_tag()) { foo(); }
        void foo();
    };
    

    Wurstinator schrieb:

    Oder aber A wird aufgeteilt in einen vererbbaren abstrakten Teil, von dem auch B erben kann, und einen Rest (der sich ggf. im entsprechenden Konstruktor erschöpft).

    Versteh ich nicht 😕

    Im einfachsten Fall sieht das ungefähr so aus:

    class widget_base
    {
    public:
         virtual widget_base() = 0; // pure nicht zwingend
    protected:
         widget_base() { ... }
    ... // der wiederverwendbare Teil des widget-codes
    };
    
    class A : public widget_base
    {
        A() : widget_base() { foo(); }
        void foo();
    ...
    };
    
    class B : public widget_base
    {
        B() : widget_base() { foo(); }
        void foo();
    ...
    };
    


  • Ach, so meinst du das.

    Ich denke, ich hab jetzt genug Vorschläge für Lösungen bekommen und kann mir da jetzt die beste (= am wenigsten schlechte? :p) raussuchen. Danke.


Log in to reply