Zugriff auf Attribute von abgeleiteten Klassen über Basisklassenzeiger



  • Ich habe ein Problem welches mich schon seit längerer Zeit beschäftigt. Ich versuche es anhand eines Beispiels zu beschreiben:

    Angenommen es existiert eine Basisklasse, welche als Interface für eine Reihe von konkreten Klassen dient:

    class DataInterface
    {
    public:
    	DataInterface();
    	~DataInterface();
    
    	virtual void FetchData(void) = 0;
    	virtual void ProcessData(void) = 0;
    	virtual void ClearData(void) = 0;
    }
    

    Die konkreten Klassen sehen so aus:

    class BinaryData: public DataInterface
    {
    public:
    	BinaryData();
    	~ BinaryData();
    
    	virtual void FetchData(void);
    	virtual void ProcessData(void);
    	virtual void ClearData(void);
    
    private:
    	bool m_boolData;
    }
    
    class IntegerData: public DataInterface
    {
    public:
    	IntegerData();
    	~ IntegerData();
    
    	virtual void FetchData(void);
    	virtual void ProcessData(void);
    	virtual void ClearData(void);
    
    private:
    	int m_intData;
    }
    

    Die konkreten Klassen implementieren das Interface von DataInterface. Aber die Typen der Attribute welche die konkreten Daten beinhalten unterscheiden sich (integer, bool, ...).

    Die Klassen könnten so verwendet werden:

    int main()
    {
    	int IntegerData;
    	bool BoolData;
    	DataInterface *pData1 = new BinaryData();
    	DataInterface *pData2 = new IntegerData();	
    
    	pData1->FetchData();
    	pData2->FetchData();
    
    	pData1->ProcessData();
    	pData2->ProcessData();
    
    	// now I want to get the data of pData1 and pData2, for example to write it into a file, show in visualization, ...
    	IntegerData = pData2->GetData() ????
    	BoolData = pData1->GetData() ????
    }
    

    Nun das Problem:
    Wie bekomme ich die Daten der konkreten Klassen? Ich habe nur Basisklassenzeiger, also bräuchte die Klasse DataInterface eine virtuelle Getter Methode. Aber die Signatur der konkreten Getter Methoden in den abgeleiteten Klassen würde immer anders aussehen. Einmal wäre der Rückgabewert ein Integer, einmal ein Bool, ...

    Oder ist der Ansatz hier mit Vererbung zur arbeiten generell falsch?



  • Die Schnittstelle erfüllt so nicht die Bedingungen für Laufzeitpolymorphie -- GetData() hat in den beiden Kindklassen nicht die gleiche Signatur. Das Problem ist letztendlich, dass

    std::unique_ptr<DataInterface> foo;
    
    if(rand % 2 == 0) {
      foo.reset(new BinaryData());
    } else {
      foo.reset(new IntData());
    }
    
    auto bar = foo->GetData(); // Wat nu?
    

    Es ist, wenn man alle Kindklassen zur Compilezeit kennt, zwar prinzipiell möglich, die Objekte der RTTI auseinanderzuhalten (also mit dynamic_cast), aber das ist natürlich sehr widerliches Design.

    Womöglich wäre boost::variant einen Blick wert, schlimmstenfalls boost::any. Besser aber wäre, sofern das möglich ist, den Code so umzustricken, dass du zur Laufzeit nicht mehr wissen musst, was für Daten FooData genau hält (beispielsweise die Verarbeitung der Daten Teil des Interfaces machst).

    Viel mehr kann ich dir ohne genauere Kenntnis deines Anwendungsfalles leider nicht sagen.



  • virtual void* data() = 0;
    virtual std::size_t size() = 0;
    


  • bspire schrieb:

    Oder ist der Ansatz hier mit Vererbung zur arbeiten generell falsch?

    Ja.

    DataInterface *pData1 = new BinaryData();
    	DataInterface *pData2 = new IntegerData();
    

    Du "upcastest" zur Superklasse (und die ist auch noch abstrakt) und wunderst dich dann, dass du mit deren Objekten nicht mehr auf Spezifiken der Unterklassen zugreifen kannst?
    Mann, Mann.
    Warum glaubst du wohl, dass Compiler und Sprachdefinition Instanziierungen von abstrakten Klassen verbieten? Sicher nicht, weil es über einen Umweg realisiert, doch sinnvoll ist.
    Dein gewählter Titel lässt schon auf falschen Verständnis schließen.
    Wenn du deine base-class abstrakt definierst, aber gleichzeitig

    baseclass *b = new subclass();
    

    wählst, verlierst du automatisch die subclass-Typinfo in b.
    Nebenbei bemerkt arbeitet man in der OOP sinnvollerweise mit Objekten und nicht mit Zeigern darauf.
    Du tust all das, um letztendlich die per Design verlorene Ausgangstypinformation der subclass später zur Laufzeit wieder irgendwie zusammenzucasten mit irgendwelchem C++ Cast-Schrott (außer static_cast kann ich hier nichts sinnvolles erkennen, was sich nicht durch sinnvolles Klassendesign erledigen ließe). Das spricht für masochistische Veranlagung, mangelnden Überblick oder beides.
    Ein Interface wird immer durch Funktionen/Methoden beschrieben, d.h. trägt deklarativen Charakter, niemals durch Daten; hast du dich mal gefragt, warum es <virtual> nicht für Membervariablen gibt?
    Genau, aus dem o.g. Grund, weil Variablen definierenden Charakter haben und somit nicht zu (abstrakten) Schnittstellenbeschreibungen passen.
    Du solltest also nicht die Membervariablen in den Vordergrund stellen und mit denen direkt irgendwie rumfrickeln sondern konsequent mit Objekten deiner Klassen arbeiten und dann klappt das auch, also in etwa:

    class DataInterface
    {
    public:
        virtual void FetchData(void) = 0;
        virtual void ProcessData(void) = 0;
        virtual void ClearData(void) = 0;
        virtual void WorkWithMember(void)=0;
    };
    
    class BinaryData: public DataInterface
    {
        bool m_boolData;
    public:
    
        void FetchData(void){};
        void ProcessData(void){};
        void ClearData(void){};
        void WorkWithMember(void){m_boolData=true;};
    };
    
    class IntegerData: public DataInterface
    {
        bool m_intData;
    public:
    
        void FetchData(void){};
        void ProcessData(void){};
        void ClearData(void){};
        void WorkWithMember(void){m_intData=4711;};
    };
    
    int main()
    {  // ab hier haben abstrakte Klassen NICHTS zu suchen!
        BinaryData b;
        IntegerData i;
    
        b.FetchData();
        i.FetchData();
    
        b.ProcessData();
        i.ProcessData();
    
        b.WorkWithMember();
        i.WorkWithMember();
    }
    


  • Wutz, selten so einen Blödsinn gelesen - oder sollte das ein verfrühter Aprilscherz von dir sein?

    Nur mal ein paar Stichworte für dich zum nachlesen: Factory(-Method), Prototype, Composite-Pattern bzw. generell Container von Basisklassenzeigern (bzw. Shared- oder Unique-Pointern).
    Wie willst du die denn mit "ab hier haben abstrakte Klassen NICHTS zu suchen!" realisieren???
    Also besser mal an die eigene Nase packen und nochmal ein paar C++ (Design) Bücher oder https://en.wikibooks.org/wiki/C%2B%2B_Programming/Code/Design_Patterns lesen...



  • Wutz schrieb:

    Nebenbei bemerkt arbeitet man in der OOP sinnvollerweise mit Objekten und nicht mit Zeigern darauf.

    🙄 lol?



  • 1. Der Destruktor der Basisklasse sollte virtual sein
    2. Such mal nach Visitor Pattern


Log in to reply