Zugriff auf mehrere Interfaces über einzelnen Zeiger



  • 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.



  • Ich würde eher einen Adapter dazwischenschalten, bevor ich die View-Klasse zum Template machen und/oder ihr einen Template-Ctor verpasse.

    Die View-Klasse zum Template zu machen wäre auch doof denke ich, da man ja vermutlich das "betrachtete" Widget zur Laufzeit auswechseln können soll. Und wenn dann nicht alle Widget zwischen denen man wechseln will den selben Typ haben, geht das nicht mehr so ohne Weiteres.

    class TextViewSourceBase
    {
    public:
        // einfache Variante:
        virtual StringAttributable& GetStringAttributable() = 0;
        virtual Widget& GetWidget() = 0;
    
        // die aufwendigere Variante wäre alle Funktionen weiterzuleiten
    };
    
    template <class T>
    class TextViewSource : public TextViewSourceBase
    {
        // kann man dann spezialisieren wie man will, evtl. mit Hilfe von 
        // boost::is_base_and_derived und diversen Template-Tricks
    };
    
    class TextView
    {
    public:
        explicit TextView(shared_ptr<TextViewSourceBase> const& source);
        void SetSource(shared_ptr<TextViewSourceBase> const& source);
    };
    

    Das Doofe an der Sache: man kann die Adapter nicht by-value rumreichen, da der konkrete Typ nicht feststeht. D.h. man muss entweder klonen oder Ownership übergeben (finde ich immer doof) oder eben Shared-Ownership verwenden (shared_ptr etc.).



  • Mit dem Template hast du Recht, direkt hätte es ein paar Nachteile. Beim Adapter sehe ich allerdings keinen grossen Unterschied mehr zu den beiden rohen, separaten Pointers. Man hat da halt ein einzelnes Objekt mit eigener Speicherverwaltung (als shared_ptr oder so eingepackt). Jedoch sind bei mir Speicherverwaltung und Besitzverhältnisse nicht gerade ein Problem, da vorläufig nur passive Verweise im Spiel sind.



  • Dann nen Template-Ctor mit enable_if<is_base_and_derived<A, X>::value && is_base_and_derived<B, X>::value> (sinngemäss).



  • Die Vererbungsbeziehung könnte doch implizit geprüft werden, wenn ich im Konstruktor zwei automatische Upcasts X* -> A* und X* -> B* habe...



  • Wenn du keine weiteren Ein-Parameter-Template-Ctors brauchst, dann ja.



  • Stimmt, SFINAE ist sonst nicht dabei. Danke für den Hinweis.



  • Wie sieht denn ein Aufruf in der TextView auf den Button aus?


Anmelden zum Antworten