Zugriff auf mehrere Interfaces über einzelnen Zeiger



  • Guten Abend miteinander, ich habe eine Frage zu Polymorphie im Zusammenhang mit Mehrfachvererbung. Gegeben ist folgende Vererbungsbeziehung:

    // A und B sind im Prinzip Interfaces
    class D : public A, public B { ... };
    

    Zusätzlich habe ich eine Client-Klasse C , die mit Objekten arbeitet, welche von beiden abstrakten Klassen A und B abgeleitet sind. Momentan besitzt C zwei polymorpher Zeiger (1 pro Interface), die beide auf das gleiche Objekt (z.B. vom dynamischen Typ D ) verweisen. Diesen Ansatz finde ich jedoch nicht sehr schön, weil man im Prinzip redundante Informationen speichert und innerhalb von C jeweils unterscheiden muss, über welchen Zeiger man auf die Funktionalität zugreift.

    class C
    {
        A* a;
        B* b;
    };
    

    Im Prinzip suche ich eine Möglichkeit, über einen Zeiger (kann auch ein Objekt mit Zeigersemantik sein) die virtuellen Methoden von A und B anzusprechen, ohne das konkrete Objekt D zu kennen. Also ohne dass die Klasse C ein Template wird. Gut wäre auch, ich müsste die bestehende Vererbungshierarchie von A , B , D nicht verändern. Mir ist klar, dass sich das Problem mit einer abstrakten Zwischenklasse, die von A und B erbt und als Basisklasse von D agierte, lösen würde.

    Oder nochmals zur Verdeutlichung, gut wäre sowas:

    // Abstrakte Hilfsklasse für Zugriff auf Funktionen von A und B
    class AB : public A, public B { ... };
    
    class C
    {
        AB* ab; // Initialisierung mit gecastetem Zeiger D* -> A* -> AB*
    };
    

    Nur führt das meines Wissens zu undefiniertem Verhalten: Der Downcast A* -> AB* ist ungültig, weil der dynamische Typ nicht AB oder davon abgeleitet, sondern D ist.

    Hat jemand eine Idee oder einen Alternativvorschlag, da das gewünschte Verhalten wahrscheinlich nicht 1:1 realisierbar ist? Würdet ihr bei zwei einzelnen Zeigern bleiben?



  • Ich bin mir nicht sicher, aber ich nehme einfach an, dass die Clientklasse C nicht direkt auf D zugreifen darf. Meine Idee wäre nun der pImpl (Pointer to implementation). Dort könntest du dir zwei Methoden implementieren welche die Basistypen zurückgeben und die dynamische Klasse vor dem Client schützen. Somit würde Klient nur noch einen Zeiger haben. Schneller macht es das Konzept sicher nicht 😉



  • Man könnte sich einen richtig smarten smart pointer bauen, der die Schnittstellen erbt und sie an seinen pointer delegiert oder entsprechnde Konvertierungsoperatoren anbietet. Für die Frage, ob man das wirklich kann ist mir der Tach aber schon eindeutig zu alt. Vielleicht morgen früh nochmal drüber nachdenken 😉



  • NichtSicherObsNutzt schrieb:

    Ich bin mir nicht sicher, aber ich nehme einfach an, dass die Clientklasse C nicht direkt auf D zugreifen darf.

    Ja, wir können annehmen, dass nur die beiden polymorphen Zeiger A* und B* bekannt sind. Es geht dabei weniger um Kapselung im Sinne von private als um Abstraktion (Polymorphie durch Basisklassenzeiger). D ist nur eine mögliche Klasse.

    NichtSicherObsNutzt schrieb:

    Meine Idee wäre nun der pImpl (Pointer to implementation). Dort könntest du dir zwei Methoden implementieren welche die Basistypen zurückgeben und die dynamische Klasse vor dem Client schützen. Somit würde Klient nur noch einen Zeiger haben.

    Du meinst wahrscheinlich sowas?

    class Impl
    {
        ... // Implementierung
        public:
            A* GetA();
            B* GetB();    
    };
    

    Zwar hätte dann C nur noch einen Impl* als Zeiger, allerdings müsste man die beiden Basistypen immer noch separat ansprechen, nur halt indirekt. Ich versuche, über einen Zeiger (oder was auch immer) auf die Schnittstellen von beiden Interfaces zuzugreifen, am besten ohne Fallunterscheidung.

    brotbernd schrieb:

    Man könnte sich einen richtig smarten smart pointer bauen, der die Schnittstellen erbt und sie an seinen pointer delegiert oder entsprechnde Konvertierungsoperatoren anbietet.

    An sowas wie Smart-Pointers habe ebenfalls ich gedacht, daher mein Kommentar bezüglich "Objekt mit Zeigersemantik". Jedoch bin ich bisher bei der Vererbung nicht weitergekommen, weil dadurch unportable Cross-Casts wie im Beispiel mit AB erforderlich sind. Mehrere Konvertierungsoperatoren fallen wohl auch aus, da die Überladungsauflösung nicht anhand auf dem Objekt aufgerufener Memberfunktionen durchgeführt wird.

    Es kann sehr gut sein, dass es keine C++-Lösung für genau dieses Problem gibt. Aber kennt vielleicht jemand ein Design-Pattern oder eine Technik, mit der man etwas Ähnliches erreichen kann? Sowas dürfte generell in der OOP ab und zu vorkommen, wenn mehrere Interfaces implementiert werden. Würde man z.B. in Java ebenfalls mehrere Referenzen unterschiedlichen Typs auf das gleiche Objekt benutzen?



  • Flyweight und Mediator sind deinem Design ähnlich. Aber soweit ich dies überblicke werden diese dein Problem nicht lösen können.



  • Nexus schrieb:

    Zusätzlich habe ich eine Client-Klasse C , die mit Objekten arbeitet, welche von beiden abstrakten Klassen A und B abgeleitet sind.

    Das heißt, es gibt mehrere andere Klassen ähnlich C, die alle von A und B (und eventuell noch anderen) abgeleitet sind?

    Wenn C mit den Methoden von A und B arbeiten können soll, ist eigentlich die Klasse AB deren Vereinigung und folglich D wiederum von AB abgeleitet.

    Aber warum:

    ohne das konkrete Objekt D zu kennen.

    Auf wessen virtuelle Methoden soll C zugreifen, wenn nicht die von einem konkreten Objekt D (oder AB oder E)?

    Mach mal ein Beispiel, was für Klassen oder Methoden das so sind und warum C D nicht kennen soll.



  • NichtSicherObsNutzt schrieb:

    Flyweight und Mediator sind deinem Design ähnlich. Aber soweit ich dies überblicke werden diese dein Problem nicht lösen können.

    Flyweight geht in eine andere Richtung, Mediator muss ich mir mal genau anschauen. Aber auf den ersten Blick sehe ich es auch so, dass diese Patterns eher nicht auf mein Design zutreffen. Aber vielen Dank für die Hinweise!

    minastaros schrieb:

    Das heißt, es gibt mehrere andere Klassen ähnlich C, die alle von A und B (und eventuell noch anderen) abgeleitet sind?

    Nein, das "welche" bezog sich auf "Objekte". C ist eine unabhängige Klasse. Zusätzlich gibt es Klassen, die eines der Interfaces A oder B implementieren, sowie solche, die beide implementieren (z.B. die Klasse D ). Um letztere geht es in diesem Beispiel.

    minastaros schrieb:

    Wenn C mit den Methoden von A und B arbeiten können soll, ist eigentlich die Klasse AB deren Vereinigung und folglich D wiederum von AB abgeleitet.

    So soll es ja nicht sein, wegen

    Nexus schrieb:

    Gut wäre auch, ich müsste die bestehende Vererbungshierarchie von A, B, D nicht verändern. Mir ist klar, dass sich das Problem mit einer abstrakten Zwischenklasse, die von A und B erbt und als Basisklasse von D agierte, lösen würde.

    minastaros schrieb:

    Aber warum:

    ohne das konkrete Objekt D zu kennen.

    Auf wessen virtuelle Methoden soll C zugreifen, wenn nicht die von einem konkreten Objekt D (oder AB oder E)?

    Auf die virtuellen Methoden zweier abstrakter Basisklassen (Interfaces), hier von A und B . Zur Veranschaulichung des Status Quo (kein UML):

    +---+   +---+
    | A |   | B |           +-----+
    +---+   +---+           |  C  |
      |       |             +-----+
      +---+---+             | A* -+--------->+---+
          |                 | B* -+--------->| D |
        +---+               +-----+          +---+
        | D |               
        +---+
    

    Ganz normale Polymorphie über Basisklassenzeiger, nur halt eben in Kombination mit Mehrfachvererbung. Dadurch zeigen A* und B* auf das gleiche Objekt.



  • Naja, mit "Beispiel" meinte ich eher eine Idee, in welche Richtung das gehen soll, A, B oder D sind halt recht abstrakt. Also z.B. sind A und B eher "Eigenschaften", so z.B.:

    A: HatVierBeine
    B: Nahrungsaufnahme

    und deren Methoden sind dann z.B.:

    A::rennen( )
    A::gassiGehen( )

    B::fressen( )
    B::schlabbern( )

    demnach wäre D z.B. ein Hund - oder ein Meerschweinchen (und im nicht erlaubten Fall einer gemeinsamen Klasse AB wäre diese dann ein "Kuscheltier").

    So, und mit C willst Du dann direkt auf die Basisklassenmethoden zugreifen, ohne direkt einen Pointer auf D zu haben, richtig? Also im Beispiel alle Objekte A::rennen lassen oder B::fressen, auch wenn es kein D ist sondern ein E ( E = "Kegelbruder" ).

    Deine Aufgabe klingt sehr interessant, ich versuche nur zu verstehen, woher Du auf Deine Einschränkung (keine Klasse AB) kommst bzw. was Du damit bezweckst. Das heißt: Gib den Dingen mal sprechende Namen! 😉



  • Am besten ist es wohl, ich nehme gleich das Beispiel, das mich überhaupt zu dieser Designfrage gebracht hat. Und zwar geht es um eine kleine GUI-Bibliothek, die ich entwickle.

    Ich habe nun eine Klasse Button (-> D), die von den Klassen Widget (-> A) und StringAttributable (-> 😎 erbt. Erstere ist die Basisklasse für GUI-Komponenten, zweitere stellt Funktionalität zur Speicherung von Text zur Verfügung. In der View-Klasse für Text (-> C; Klasse, welche die grafische Darstellung von Text übernimmt) brauche ich nun beide Schnittstellen, weil Widget Informationen über Grösse und Position der Komponente speichert und StringAttributable den Text und dessen Formatierung kennt. Ich frage also in der View-Klasse TextView die Eigenschaften von Button über die beiden Basisklassen Widget und StringAttributable ab.

    class Button : public Widget, public StringAttributable { ... };
    
    class TextView
    {
        Widget*             a;
        StringAttributable* b;
    };
    

    P.S.: Wenn jemand einen besseren Namen für StringAttributable kennt, wäre ich ihm/ihr sehr verbunden. 🙂



  • Denkst du nicht irgendwie in die falsche Richtung? Wenn dein Design die beiden Interfaces trennt, sollte doch eine Klasse, die beide benutzt, nicht davon ausgehen, dass sie zusammenhängen. Du könntest ja z.B. Button von Widget ableiten, aber StringAttributable als Member implementieren.



  • manni66 schrieb:

    Wenn dein Design die beiden Interfaces trennt, sollte doch eine Klasse, die beide benutzt, nicht davon ausgehen, dass sie zusammenhängen.

    Das ist ein guter Punkt. Normalerweise müssen die Interfaces nicht derart zusammenhängen. In meinem Code kann allerdings davon ausgegangen werden, dass beide polymorphen Basisklassenzeiger auf das gleiche Objekt zeigen. Diese Beschränkung ist etwas unschön, vielleicht gibt es ja eine sehr elegante Alternative.

    manni66 schrieb:

    Du könntest ja z.B. Button von Widget ableiten, aber StringAttributable als Member implementieren.

    Ich habe hier bewusst Vererbung statt Komposition gewählt, aber im Prinzip hast du Recht: Man ist wahrscheinlich flexibler mit zwei separaten Zeigern, vor allem im Hinblick auf künftige Design-Umstellungen. Trotzdem bin ich diesbezüglich etwas skeptisch, zwei Zeiger auf das gleiche Objekt zu haben und durch jeden nur die Hälfte der Funktionalität zu nutzen.

    Falls jemand bereits vor einem ähnlichen Problem gestanden ist oder einen guten Link dazu kennt, würde ich mich über eine Antwort freuen. Natürlich gilt das für alle Vorschläge! 😉



  • Du kannst dir den 2. Zeiger durch dynamic_cast aus dem ersten zaubern.

    Allerdings würde ich trotzdem empfehlen beide Zeiger als Member zu halten, da dynamic_cast bei einigen Implementierungen nicht unbedingt schnell ist. Bei jedem Funktionsaufruf zu Casten wäre daher u.U. eine Bremse (je nachdem wie CPU Aufwändig die Funktionen sind, bei sehr einfachen Funktionen auf jeden Fall).

    Falls jemand bereits vor einem ähnlichen Problem gestanden ist oder einen guten Link dazu kennt, würde ich mich über eine Antwort freuen. Natürlich gilt das für alle Vorschläge!

    Kannst du etwas näher darauf eingehen was für Funktionen in den beiden Interfaces drinnen sind?



  • hustbaer schrieb:

    Du kannst dir den 2. Zeiger durch dynamic_cast aus dem ersten zaubern.

    Interessant, wieder was gelernt! Aber ich habe schon beide Zeiger A* und B* , das ist nicht das Problem.

    hustbaer schrieb:

    Kannst du etwas näher darauf eingehen was für Funktionen in den beiden Interfaces drinnen sind?

    Sicher. Leicht vereinfacht (ohne Namespaces, Const-Referenzen etc.) sieht das so aus:

    class Widget
    {
      public:
        // Position des Mittelpunkts
        void      SetPosition(Vector2f position);
        Vector2f  GetPosition() const;
    
        // Abmessungen der Komponente (umschliessendes Rechteck)
        void      SetSize(Vector2f size);
        Vector2f  GetSize() const;
    
        // Mit Benutzer interagierbar / grau hinterlegt?
        void      SetEnabled(bool enabled);
        bool      IsEnabled() const;
    };
    
    class StringAttributable
    {
      public:
        // String des darzustellenden Textes
        void      SetString(String);
        String    GetString() const;
    
        // Text-Ausrichtung (linksbündig, zentriert, ...)
        void      SetTextAlignment(TextAlign alignment);
        TextAlign GetTextAlignment() const;
    };
    


  • Hm, Du musst ja dem einzelnen Pointer in C (bzw. TextView) einen Typ geben. Und das kann (nach Definition) weder A noch B sein.

    Du könntest allerdings tatsächlich eine Hilfsklasse AB erstellen, die sich genauso wie Button auch von Widget und StringAttributable ableitet. Das stände dann auf einer Ebene wie Button.

    Der Pointer in TextView könnte dann von diesem Typ sein. Nun musst Du TextView noch den Button mitteilen, vielleicht in der Art

    myTextView.setAB( reinterpret_cast< AB* >( &myButton );
    

    In TextView solltest Du dann alle Methoden über den Pointer aufrufen können. dynamic_cast hat hier nicht funktioniert, nur der reinterpret. Allerdings ist das ein wüstes Gecaste. Und wie sich das verhält, wenn Button jetzt noch von einer weiteren Klasse erbt, AB aber nicht --- noch nicht ausprobiert.

    Gute Nacht erstmal.



  • minastaros schrieb:

    Hm, Du musst ja dem einzelnen Pointer in C (bzw. TextView) einen Typ geben. Und das kann (nach Definition) weder A noch B sein.

    Genau, vor diesem Problem stehe ich auch. 😉

    Wie schon angedeutet ist das kaum 1:1 mit einem einzelnen Zeiger vernünftig lösbar. Aber vielleicht übersehe ich grundlegende Alternativen. Das mit dem reinterpret_cast wäre ja ähnlich wie mein Versuch mit AB – beides undefiniert.



  • @minastaros:
    Probier dein wildes Gecaste mal wenn das Widget von B, A erbt, AB aber A, B.
    Dann knallts nämlich hübsch 🙂

    -> Finger davon lassen

    @Nexus:
    OK. Und welche Klasse möchte nun sowohl auf Widget als auch auf StringAttributable zugreifen?



  • hustbaer schrieb:

    @Nexus:
    OK. Und welche Klasse möchte nun sowohl auf Widget als auch auf StringAttributable zugreifen?

    Die View-Klasse TextView , welche Text darstellt. Widget benötige ich vor allem für die Abmessungen (Grösse und Position), StringAttributable für text-spezifische Merkmale (String, Formatierung). Diese Eigenschaften der GUI-Komponente werden über die beiden polymorphen Basisklassenzeiger von TextView abgefragt.



  • hustbaer schrieb:

    Dann knallts nämlich hübsch 🙂
    -> Finger davon lassen

    Deshalb ist die ganze Geschichte mit Casten und somit Aufbrechen der Klassen etwas fragwürdig. Aber ein Cast wäre halt der einzige Weg, um eine gemeinsame abgeleitete Klasse AB zu vermeiden, denn irgendeinen Typ muss der Pointer ja bekommen.

    Mal ein ganz anderer Ansatz:
    Wenn die Anzahl der zu veröffentlichenden Methoden, die TextView benötigt, begrenzt ist, könnte man auch Widget und StringAttributable von einer gemeinsamen Klasse ableiten (hier: Graph). Diese würde virtuell alle benötigten Methoden implementieren (das ist der Nachteil, aber wie gesagt, wenn es um eine begrenzte Anzahl geht...). Widget und StringAttributable (oder auch andere) können dann die Methoden überschreiben oder nicht.

    Der Pointer in TextView jedenfalls wäre dann vom Typ Graph, und über ihn kann man jede Methode gefahrlos aufrufen. Ein Button kann eine oder beide der "Eigenschaftsklassen" implementieren, muss es aber nicht, oder kann noch von weiteren Klassen erben. Die Ableitungen sind jedenfalls alle "ein Graph".

    Hier mal ein Versuch mit einer zusätzlichen Klasse "Label", bei der die Vererbungsreihenfolge versuchsweise getauscht ist. TextWiew hab ich mal Referenzen gegeben, aber geht mit Pointern auch (man verzeihe mir die kastrierten Setter-Methoden und fehlende Destructoren...):

    /* Inheritance tree:
    
                 +-------+         +----------+
                 | Graph | · · · · | TextView |
                 +-------+         +----------+
                  /     \
          +--------+   +-------------+
          | Widget |   | StringAttr. |
          +--------+   +-------------+
                 \  / \  /
         +---------+   +-------+
         | Button  |   | Label |
         +---------+   +-------+
    
     */
    
    class Graph
    {
    public:
        virtual void SetPosition( )                     { return; }
        virtual void SetSize( )                         { return; }
    
        virtual void SetString( )                       { return; }
        virtual void SetTextAlignment( )                { return; }
    };
    //____________________________________________________________________________
    
    class Widget : virtual public Graph
    {
    public:
        void SetPosition( )                     { cout << "Got Position." << endl; }
        void SetSize( )                         { cout << "Got Size." << endl; }
    };
    //____________________________________________________________________________
    
    class StringAttributable : virtual public Graph
    {
    public:
        void SetString( )                       { cout << "Got String." << endl; }
        void SetTextAlignment( )                { cout << "Got SetTextAlignment." << endl; }
    };
    //____________________________________________________________________________
    
    class Button : public Widget, public StringAttributable
    {
    public:
        Button( );
    };
    
    Button::Button( )
    : Graph( )
    , Widget( )
    , StringAttributable( )
    {
        cout << "Hello I am a Button." << endl;
    }
    //____________________________________________________________________________
    
    class Label : public StringAttributable, public Widget
    {
    public:
        Label( );
    };
    
    Label::Label( )
    : Graph( )
    , StringAttributable( )
    , Widget( )
    {
        cout << "Hello I am a Label." << endl;
    }
    //____________________________________________________________________________
    
    class TextView
    {
    public:
        TextView( Graph& graph_ ) : graph( graph_ ) { }
        void doSomething( );
    private:
        Graph& graph;
    };
    
    void TextView::doSomething( )
    {
        graph.SetPosition( );
        graph.SetSize( );
        graph.SetString( );
        graph.SetTextAlignment( );
    }
    //____________________________________________________________________________
    
    int main( )
    {
    	Button myButton;
    	TextView myViewButton( myButton );
    	myViewButton.doSomething( );
    
        Label myLabel;
        TextView myViewLabel( myLabel );
        myViewLabel.doSomething( );
    
    	return 0;
    }
    

    Ausgabe:
    `Hello I am a Button.

    Got Position.

    Got Size.

    Got String.

    Got SetTextAlignment.

    Hello I am a Label.

    Got Position.

    Got Size.

    Got String.

    Got SetTextAlignment.`



  • @Nexus:
    Gibt es Konstellationen in denen es unpraktisch wäre die Text-Widgets von einer "TestWidget" Klasse ("AB") abzuleiten?
    Wenn nein, dann würde ich das so machen.

    Sonst würde ich - wenn sonst nichts dagegen spricht - eine Möglichkeit schaffen nur den Widget-Zeiger zu übergeben, wobei die TextView sich den StringAttributable-Zeiger dann selbst castet.
    Intern hält sich die TextView dann zwar zwei Zeiger, aber für den User ist es nicht unnötig umständlich, weil er nur einen übergeben muss.



  • minastaros schrieb:

    Mal ein ganz anderer Ansatz:
    Wenn die Anzahl der zu veröffentlichenden Methoden, die TextView benötigt, begrenzt ist, könnte man auch Widget und StringAttributable von einer gemeinsamen Klasse ableiten (hier: Graph). Diese würde virtuell alle benötigten Methoden implementieren (das ist der Nachteil, aber wie gesagt, wenn es um eine begrenzte Anzahl geht...).

    Ich würde die beiden Interfaces schon gerne unabhängig halten. GUI-Komponenten können z.B. durchaus von Widget erben, aber nicht von StringAttributable . Und Dummy-Methoden, die nichts tun (oder sowas wie UnupportedOperationException werfen), gehen mir stark gegen den Strich. 😉

    hustbaer schrieb:

    Gibt es Konstellationen in denen es unpraktisch wäre die Text-Widgets von einer "TestWidget" Klasse ("AB") abzuleiten?
    Wenn nein, dann würde ich das so machen.

    Mir fällt da auf die Schnelle nichts ein, aber ich habe eher grundlegendere Bedenken: Der Vorteil, durch Mehrfachvererbung unabhängige Klassen zu kombinieren, ohne Zusatzcode zu schreiben, geht verloren. Ich habe noch andere Interfaces, und es werden wohl noch ein paar dazukommen – der Gedanke, für jede benötigte Kombination eine zusätzliche Klasse zu schreiben, gefällt mir nicht besonders. Gerade da ich die Vererbungshierarchie einigermassen flach und intuitiv halten möchte. 🙂

    hustbaer schrieb:

    Sonst würde ich - wenn sonst nichts dagegen spricht - eine Möglichkeit schaffen nur den Widget-Zeiger zu übergeben, wobei die TextView sich den StringAttributable-Zeiger dann selbst castet.
    Intern hält sich die TextView dann zwar zwei Zeiger, aber für den User ist es nicht unnötig umständlich, weil er nur einen übergeben muss.

    Ja, das wäre eine Möglichkeit. Oder halt ein Template-Konstruktor, falls der konkrete Typ bekannt sein sollte... Wobei ich dann auch die Klasse zum Template machen könnte. Naja...

    Bisher ist ja schon einiges zusammengekommen. Danke schon mal an alle für die Vorschläge und die investierte Zeit (besonders minastaros und hustbaer)! Mal schauen, wie ich das nun löse... Vielleicht nehme ich vorerst die zwei Zeiger und behalte mir andere Möglichkeiten im Hinterkopf. Kann auch sein, dass sich das Ganze plötzlich durch irgendeine Design-Entscheidung erübrigt.


Log in to reply