Überschriebene und überladene Methoden aus Basisklasse aufrufen



  • Hallo,

    ich möchte gerne - möglichst elegent - folgendes machen: Ich habe Klassen, die ich als Parameter nutzen möchte, etwa P1 und P2. P1 und P2 erben dabei beide von PBase.

    Jetzt habe ich noch eine Klasse S und SBase, dabei erbt S von SBase. Diese Klassen sollen so aussehen:

    class SBase {
    public:
      void doIt(PBase* p) {
        this->internalDoIt(p);
      }
      virtual void internalDoIt(PBase* p) = 0;
    }
    
    class S : public SBase {
    public:
      virtual void internalDoIt(PBase* p) {};
      virtual void internalDoIt(P1* p) {};
      virtual void internalDoIt(P2* p) {};
    }
    

    Jetzt möchte ich an einem Objekt von SBase mit Objekten von P1 oder P2 die doIt()-Mehtode aufrufen. Diese soll dann die bestmöglichste überladene Methdoe aus S aufrufen. Im obigen Beispiel würde ja immer S::internalDoIt(PBase* p) aufgerufen werden, nie die P1 oder P2 Version (das wäre mein Wunsch).

    Warum das alles so ist, ist mir klar. Was mir nicht klar ist, wie ich möglichst elegant mein Ziel erreiche. Ich möchte nicht bereits in SBase alle internalDoIt für alle P-Klassen definieren (dann würde Double-Dispatch helfen)... sondern dass soll erst in den Ableitungen von SBase passieren.

    Wie kann das am elegantesten erreichen? Ohne if-ketten mit versuchten dynamic_casts? Geht da was mit cooler Template-Magic? Als Java-Entwickler würde ich hier Refelection nutzen - ich bin mir sicher, das aber auch in C++ das schick gemacht werden kann.

    Viele Dank für eure Hilfe,

    C++-Newb



  • Ich versteh dein Beispiel nicht ganz:
    doIt wird immer void internalDoIt(PBase* p) aufrufen, nie etwas anderes.


  • Mod

    Wie kann das am elegantesten erreichen? Ohne if-ketten mit versuchten dynamic_casts?

    Kommt halt drauf an, was du unter Eleganz verstehst.

    #include <iostream>
    #include <cassert>
    
    enum class PType
    {
    	pbase, p1, p2
    };
    
    struct PBase
    {
    	PType const type;
    
    	PBase() : PBase{PType::pbase} {}
    
    protected:
    	PBase( PType type ) : type{type} {}
    };
    
    struct P1 : PBase { P1() : PBase{PType::p1} {} };
    struct P2 : PBase { P2() : PBase{PType::p2} {} };
    
    struct SBase
    {
    	virtual ~SBase() = default;
    
    	void doIt(PBase* p)
    	{
    		internalDoIt(p);
    	}
    
    	virtual void internalDoIt(PBase* p) = 0;
    };
    
    struct S : SBase
    {
    	virtual void internalDoIt( PBase* p )
    	{
    		switch( p->type )
    		{
    			case PType::pbase: internalDoIt(p); break;
    			case PType::p1   : internalDoIt(static_cast<P1*>(p)); break;
    			case PType::p2   : internalDoIt(static_cast<P2*>(p)); break;
    
    			default:
    				assert(false);
    		}
    	}
    
    	virtual void internalDoIt_impl(PBase*) { std::cout << "internalDoIt(Pbase*)\n"; }
    	virtual void internalDoIt_impl(P1*   ) { std::cout << "internalDoIt(P1   *)\n"; }
    	virtual void internalDoIt_impl(P2*   ) { std::cout << "internalDoIt(P2   *)\n"; }
    };
    
    #include <memory>
    #include <vector>
    
    int main()
    {
    	std::vector<std::unique_ptr<PBase>> vec;
    	vec.emplace_back( new PBase );
    	vec.emplace_back( new P1    );
    	vec.emplace_back( new P2    );
    
    	S s;
    	SBase& bref = s; // Angenommn, dass du von irgendwo her einen Basisklassen-Zeiger/Referenz hast..
    
    	for( auto& p : vec )
    		bref.doIt(p.get());
    }
    


  • Nathan schrieb:

    Ich versteh dein Beispiel nicht ganz:
    doIt wird immer void internalDoIt(PBase* p) aufrufen, nie etwas anderes.

    Das weiß ich ja 🙂

    Arcoth schrieb:

    Kommt halt drauf an, was du unter Eleganz verstehst.

    Stimmt, das hätte ich genauer erläutern sollen.

    Ich möchte zwei "Typen" von Klassen haben: Ableitungen von PBase (z. B. P1, P2) und SBase (z. B. S1, S2), auf die ich jeweils einen Zeiger habe.

    In S1 und S2 sind (beliebig viele) Methoden (internalDoIt) definiert, die einen Parameter haben (der eine Ableitung von PBase ist, also hier P1 und P2 oder PBase selbst).

    Jetzt habe ich zeiger auf Objekte von diesen Klassen und rufe eine SBase-Objekt mit einem PBase-Parameter auf:

    SBase* s = new S1();
    PBase* p = new P2();
    s->doIt(p);
    

    Ich möchte nun, dass irgendwie (in diesem Fall) S1::internalDoIt(P2* p2) aufgerufen wird.

    Mit "elegant" meine ich, dass die Auswahl der richtigen internalDoIt-Methode irgendwie in PBase oder SBase (oder einer zusätzlichen "Framework"-Klasse) passieren soll, und nicht in P1, P2, S1 oder S2.

    Das, Arcoth, ist was ich in deinem Vorschlag nicht als elegant betrachte, denn wenn ich jetzt eine neue Ableitung von PBase einführe (z. B. P3), dann muss ich:
    * Enum PType erweitern
    * internalDoIt von S1 erweitern
    * internalDoIt_impl für S1+P3 schreiben.

    Dabei ist es doch nur der letzte Punkt, den ich wirklich machen möchte (=elegant -> bei einer Erweiterung muss ich nur eine Stelle ändern).

    Gibt es dafür Template-Techniken, mit denen man das umsetzen kann?



  • C++-Newb schrieb:

    Diese soll dann die bestmöglichste überladene Methdoe aus S aufrufen.

    Verstehe ich nicht so richtig. Du hast doch schon die unterschiedlichen P* Objekte in der Hand.
    Warum nutzt du das nicht aus und machst du typabhängige Arbeit dort, wo sie hingehört?
    Falls du dabei noch irgendwas von S* brauchst, übergibst du einfach die Referenz.

    Casts sind Müll; sie zeugen nur von mangelndem Überblick von Daten/Codedesign und sind bestimmt nicht "elegant".
    Zeiger sind ebenso Müll genau da, wo auch Referenzen möglich sind.
    Instanzen von Basisklassen sind auch Müll und ebenso nicht "elegant".

    class SBase
    {
      virtual void work()=0;
    };
    
    class PBase
    {
      virtual void doit(SBase&)=0;
    };
    class P1: public PBase
    {
      public:
      void doit(SBase&s) {cout << "P1" << '\t' << &s <<'\n';};
    };
    class P2: public P1
    {
      public:
      void doit(SBase&s) {cout << "P2" << '\t' << &s <<'\n';};
    };
    
    class S : public SBase
    {
      public:
      void work() {
      P1 p1;
      P2 p2;
      p1.doit(*this);
      p2.doit(*this);
      }
    };
    
    int main()
    {
        S s;
        s.work();
        return 0;
    }
    

    http://ideone.com/BzD0Sy



  • Was spricht nochmal genau gegen double dispatch? => http://ideone.com/b86vPv

    Wenn du jetzt P3 einführst, dann musst du nur:
    * accept für P3 schreiben
    * PBaseVisitor um P3 erweitern
    * internalDoItImpl für S1+P3 schreiben.

    Andere Frage: Was brauchst du alles zwingend zur Laufzeit? Warum geht S1<P1> nicht oder X<S1,P1>?



  • Mein Ansatz: http://ideone.com/GPEukc
    Es wird eine Map verwendet, die Funktionen auf Typen mappt.
    Ein Template sorgt für elegante Initialisierung.
    Ansatz ist sehr grob, bei Bedarf können auch Memberfunktionszeiger o.ä. verwendet werden.



  • @Wutz: Die unterschiedlichen Objekte habe ich ja nur durch eine Referenz/Zeiger auf eine gemeinsame Basisklasse...

    @doublemismatch: Ja, double disptach geht schon, aber dann muss ich halt bei neuer P-Klasse noch etwas an einer anderen Stelle ändern. Aber in deinem Beispiel sieht das ja doch nicht so schlimm aus, wahrscheinlich ist die gute Lesbarkeit des ganzen dann doch etwas Wert.

    @Nathan: Vielen Dank, so habe ich mir das ungefähr vorgestellt. Ich bastel daran mal noch etwas weiter.


Anmelden zum Antworten