Über Basisklassenzeiger entscheiden welche spezialisierte Funktion aufgerufen werden soll


  • Mod

    Ich habe eine Reihe von Objekten bei denen es sich absolut anbietet sie alle über eine gemeinsame Basisklasse anzusprechen. Bei allen Anwendungen bis auf eine:

    Angenommen die Klassen heißen: A, B, C und erben alle von Base.
    Dann habe ich jetzt in meinem Programm eine einzige Stelle wo ich die Instanzen von A, B, C nicht einfach polymorph über Base-Zeiger benutzen kann. Und zwar habe ich eine Funktion mit zwei Argumenten, nennen wir sie foo(Base,Base). Problem: Es gibt auch spezialisierte Funktionen von foo(), meinetwegen foo(A,B), foo(C,C). Wie entscheide ich jetzt am einfachsten, welche Funktion aufzurufen ist? Eine virtuelle Memberfunktion reicht nicht aus, da ich ja zwei Argumente habe.

    Eine Kaskade von dynamic_casts fände ich äußerst umständlich.

    Ich sollte noch ausdrücklich erwähnen, dass den foo()s auch die Teile der Klassen ausreichen, die in Base gemeinsam sind, d.h. statt foo(A,B) könnte es auch genausogut foo_AB(Base,Base) heißen. Entscheidend ist, dass foo_AB() einen etwas anderen Algorithmus implementiert als foo().

    Was ist denn da die übliche Vorgehensweise? Ich kann mir nicht vorstellen, dass ich der erste mit dieser Problemstellung bin. Da gibt es doch bestimmt irgendein Entwurfsmuster welches dies elegant löst.

    Meine bisher "eleganteste" Lösung:
    Ich verpasse den Klassen noch eine virtuelle Methode GetId(), die mir einen eindeutigen Integerwert zurück gibt. Dann habe ich in meinem Programm ein zweidimensionales Array von Zeigern auf Funktionen mit zwei Base als Parameter. Und dann benutze ich jeweils den passenden Funktionszeiger:

    Klasse   GetID
    ---------------
     Base      0
      A        1
      B        2
    
    2D Array:
     |  0      1       2 
    -----------------------
    0|&foo   &foo    &foo   
    1|&foo  &foo_AA  &foo_AB
    2|&foo  &foo_BA  &foo_BB
    
    Aufruf:
    Base* ptr1 = new A;
    Base* ptr2 = new B;
    
    (*Array[ptr1->GetID()][ptr2->GetID])(*ptr1,*ptr2);  // Ruft foo_AB auf
    


  • "Eine virtuelle Memberfunktion reicht nicht aus, da ich ja zwei Argumente habe."

    Verstehe ich nicht. Eine virtuelle Memberfunktion kann auch zwei Argumente haben. Diese Funktion kann in den Unterklassen redefiniert werden.



  • Hi,

    Eine virtuelle Memberfunktion reicht nicht aus, da ich ja zwei Argumente habe.

    Und wenn du 2 virtuelle Memberfunktionen nimmst?

    Base* a = new A;
    Base* b = new B;
    
    a->foo(b);
    
    void A:foo(Base* b)
    {
       b->fooA(this);
    }
    void B:fooA(Base* a)
    {
      // hier fooAB()
    }
    


  • Überseh ich was oder willst du einfach nur nen Double Dispatch?



  • Hi SeppJ,

    auch wenn das folgende Beispiel nicht kompiliert, die Idee dahinter
    sollte klar sein...

    #include<iostream>
    #include<vector>
    
    class Foo;
    class Base;
    
    class A;
    class B;
    
    class IForwardable{
    public:
            virtual void forward(Foo& f, Base& base) = 0;
            virtual void forward(Foo& f, A& a) = 0;
            virtual void forward(Foo& f, B& b) = 0;
            virtual void forwardIt(Foo& f, Base& base) = 0;
    };
    
    class Base : public IForwardable{
    };
    
    class Foo{
    public:
            void print(Base&, Base&){
                    std::cout << "base base" << std::endl;
            };
            void print(Base&, A&){
                    std::cout << "base a" << std::endl;
            };
            void print(Base&, B&){
                    std::cout << "base b" << std::endl;
            };
            void print(A&, Base&){
                    std::cout << "a base" << std::endl;
            };
            void print(A&, A&){
                    std::cout << "a a" << std::endl;
            };
            void print(A&, B&){
                    std::cout << "a b" << std::endl;
            };
            void print(B&, Base&){
                    std::cout << "b base" << std::endl;
            };
            void print(B&, A&){
                    std::cout << "b a" << std::endl;
            };
            void print(B&, B&){
                    std::cout << "b b" << std::endl;
            };
            void printIt(Base& b1, Base& b2){
                    b1.forwardIt(*this, b2);
            }
    };
    
    template<typename T>
    class Forwardable{
    public:
            virtual void forward(Foo& f, Base& base){
                    f.print(base, dynamic_cast<T&>(*this));
            }
            virtual void forward(Foo& f, A& a){
                    f.print(a, dynamic_cast<T&>(*this));
            }
            virtual void forward(Foo& f, B& b){
                    f.print(b, dynamic_cast<T&>(*this));
            }
            virtual void forwardIt(Foo& f, Base& base){
                    base.forward(f, dynamic_cast<Base&>(*this)); // EDIT2: hier sollte natürlich forward hin
            }
    };
    
    class A : public Base, public Forwardable<A>{};
    class B : public Base, public Forwardable<B>{};
    class C : public Base, public Forwardable<Base>{};
    
    int main(){
            Foo f;
            A a;
            B b;
            C c;
            std::vector<Base*> vec;
            vec.push_back(&a);
            vec.push_back(&b);
            vec.push_back(&c);
            for(std::vector<Base*>::const_iterator it = vec.begin(); it!=vec.end(); ++it){ EDIT3: kein Kommentar
                    for(std::vector<Base*>::const_iterator it2 = vec.begin(); it2!=vec.end(); ++it2){
                            f.printIt(**it, **it2);
                    }
            }
            return 0;
    }
    

    EDIT: Ehrlich gesagt versteh ich im Moment nicht, warum er mir unimplementierte
    Funktionen anmeckert...
    Generell hätte ich auch gesagt, dass ein static_cast ausreicht 🤡

    Gruß,
    CSpille



  • Habe übersehen, dass die Argumente verschieden sind. Klar gehts um double dispatch. Wenn man keinen dynamic_cast will, scheint mir die Lösung von SeppJ noch die eleganteste.



  • Humbert schrieb:

    Habe übersehen, dass die Argumente verschieden sind. Klar gehts um double dispatch. Wenn man keinen dynamic_cast will, scheint mir die Lösung von SeppJ noch die eleganteste.

    häh...

    Hab ich was verpaßt?
    Klar mußt du in jeder Funktion casten. Du kriegst ja nur ein Base& als Parameter



  • Hab ich auch was verpasst?
    Ich dachte meine Version ist genau so, wie man Double Dispatch in C++ implementiert!?


  • Mod

    Ganz viele Leute schrieb:

    double dispatch

    Jaaa, das ist es was ich suchte. Danke an alle Helfer! 👍 Ahnte ich doch, dass es dafür eine Standardlösung geben muss.

    Ich sollte mir wohl mal ein Buch über solche Techniken durchlesen.



  • SeppJ schrieb:

    Ahnte ich doch, dass es dafür eine Standardlösung geben muss.

    Gibts nicht. Es gibt viele verschiedene Ansätze die diesen Mangel in C++ (kein double Dispatch in der Sprache eingebaut) angehen, aber alle haben ihre Vor- und Nachteile. Und keiner ist eine "Standardlösung"


Anmelden zum Antworten