Template mit später deklarierter Klasse



  • Moin!

    Da ich einen ganzen Stapel Klassen habe, die austauschbar sein müssen und sich ähnlich verhalten sollen, dabei aber gleichzeitig nicht Namensräume vollmüllen sollen, möchte ich Unterklassen im Namensraum der Oberklasse haben.

    class StandardData
    {
      int a;
    };
    
    template< typename DataType >
    class Interface
    {
      virtual bool func( DataType & bla ) = 0;
      bool func( DataInterface & bla ) { ... }  // ruft func( DataType ), falls richtiger Typ
    };
    
    class Element;
    class Element::Data;  // Fehler C2027: "Verwendung des undefinierten Typs "Element"
    
    class Element : public Interface< class Element::Data >
    {
       class Data : public StandardData
       {
         int b;
       }
    
       bool func( Data & myData );
    };
    

    Von den Element-Klassen werde ich eine ganze Reihe haben, ich möchte den Aufbau soweit wie möglich standardisieren, habe aber nun das Problem, dass ich Element nicht von Adapter< Element::Data > ableiten kann, weil Element::Data noch nicht deklariert ist.

    Hat jemand eine Idee, wie ich das formulieren kann, so dass Visual C mich versteht?

    Wie kann ich eine Klasse innerhalb des Namensraum der Klasse deklarieren, bevor diese Klasse definiert wurde?



  • [quote="Xin"

    class Interface : public Interface
    

    [/quote]
    Wie soll die Zeile wirklich aussehen?



  • Michael E. schrieb:

    Xin schrieb:

    class Interface : public Interface
    

    Wie soll die Zeile wirklich aussehen?

    Ähh... anders ^^. Hier soll es keine Ableitung geben.

    Ich habe noch eine zusätzliche Klasse dazwischen: Adapter, der vom Interface abgeleitet ist. Da ich den im Code rausschmeißen will und das nix mit dem Problem zu tun hat, habe ich das hier dann rausgenommen und den Adapter zum Interface erklärt, das keine Ableitung mehr benötigt.

    Den Code oben habe ich korrigiert. Danke 🙂



  • Du wirst dabei nicht drumrumkommen, die Element::Data als eigene Klasse zu implementieren und nicht als interne Klasse von Element.

    Xin schrieb:

    Da ich einen ganzen Stapel Klassen habe, die austauschbar sein müssen und sich ähnlich verhalten sollen, dabei aber gleichzeitig nicht Namensräume vollmüllen sollen, möchte ich Unterklassen im Namensraum der Oberklasse haben.

    Ich nehme mal an du meinst mit Unterklasse/Oberklasse Element::Data und Element. Wenn du wirklich soo viele Datenklassen hast, sollte dafür vielleicht ein eigener Namespace her. Oder du überlegst dir eine sinnvolle Trennung der verschiedenen Element-Klassen in verschiedene Namensräume und packst die zugehörigen Data-Klassen dort mit rein.



  • Erlär mal bitte, wie du dir vorstellst die Element Klasse zu verwenden.

    void foo(Interface< class Element::Data >* element)
    {
     // ...
    }
    
    int main()
    {
      Element e;
      Interface< class Element::Data >* element = new Element;
      foo(&e);
      foo(element);
    }
    

    Für mich mach das überhaupt keinen Sinn. Es wird wohl keine andere Klasse von
    Interface< class Element::Data > erben, es kann also nicht von Polymorphie die Rede sein. Jede Elementklasse, die so vom Interface Template erbt, hat ihre eigene Schnittstelle. Möchtest du dir vielelicht die Implementierung von
    bool func( DataInterface & bla ) erben? Verwende äffentliche Vererbung nicht um Implementierungen zu erben. Versuch doch mal den Inhalt von bool func( DataInterface & bla ) über Komposition wiederzuverwenden, z.B. durch einen Interface<> Member. Da wird auch direkt klar, dass der Name blöd ist, da das gar kein Interface ist.



  • pumuckl schrieb:

    Du wirst dabei nicht drumrumkommen, die Element::Data als eigene Klasse zu implementieren und nicht als interne Klasse von Element.

    Vielleicht, aber das würde mich stören.

    Die Frage ist, ob jemand eine Idee hat, wie man das trotzdem formulieren kann.

    pumuckl schrieb:

    Xin schrieb:

    Da ich einen ganzen Stapel Klassen habe, die austauschbar sein müssen und sich ähnlich verhalten sollen, dabei aber gleichzeitig nicht Namensräume vollmüllen sollen, möchte ich Unterklassen im Namensraum der Oberklasse haben.

    Ich nehme mal an du meinst mit Unterklasse/Oberklasse Element::Data und Element. Wenn du wirklich soo viele Datenklassen hast, sollte dafür vielleicht ein eigener Namespace her. Oder du überlegst dir eine sinnvolle Trennung der verschiedenen Element-Klassen in verschiedene Namensräume und packst die zugehörigen Data-Klassen dort mit rein.

    Die Klassen liegen bereits in Namespaces unterhalb von Namespaces.
    Es ist kein Problem noch einen Namespace zu basteln, allerdings wird ja auch einer durch die Klasse aufgemacht und die jeweiligen Data-Klassen passen nirgendswo besser rein, als im Namespace der einzigen Klasse, die die Subklasse verwendet.

    brotbernd schrieb:

    Erlär mal bitte, wie du dir vorstellst die Element Klasse zu verwenden.

    Das Projekt hat 150MB Quelltext. Ich habe ein Diplom der Informatik und kann objektorientiert arbeiten. Die Details auszudiskutieren, die mich dazu bringen, über ein derartiges Design nachzudenken, führt in eine endlose Offtopic-Diskussion.
    Sagen wir einfach, dass ich Teile der "Membervariablen" zur Laufzeit austauschen können möchte und jede "Element"-Klasse unterschiedliche austauschbare "Member" hat und diese entsprechend keine "Member" sein können, sondern in der Klasse "Data" beschrieben werden.

    Mein Problem ist nicht, dass ich das Programm nicht lauffähig formulieren könnte, mein Problem ist, dass ich den passendsten Namensraum nutzen möchte und bisher keinen Weg gefunden habe, das zu formulieren.

    Ob ich Implementation erben möchte oder nicht, spielt für das Problem keine Rolle, aber ja - ich will Implementation erben. Bitte keine Off-Topic-Diskussion, ob die hier angebrachten Klassennamen gefallen - die Klassen heißen im Projekt sowieso alle ganz anders - oder ob man persönlich andere Vorlieben hat.

    Die Frage ist, wie lässt sich meine Anforderung in C++ formulieren?



  • Das war keine Off-Topic Kritik, sondern ein Lösungsvorschlag, nämlich die Implementierung nicht über Vererbung sondern über Komposition zu benutzen. Dann kannst du deine Data Klasse auch in der Elementklasse definieren.
    Ansonsten bleibt dir nur -wie schon gesagt wurde- die Möglichkeit, die Data Klasse irgendwie vorher zu definieren.

    Nur interessehalber, wie groß ist so ein Stapel in 150MB denn etwa?



  • Xin schrieb:

    Die Frage ist, wie lässt sich meine Anforderung in C++ formulieren?

    Die Antwort ist wie gehabt: So garnicht.

    pumuckl schrieb:

    Du wirst dabei nicht drumrumkommen, die Element::Data als eigene Klasse zu implementieren und nicht als interne Klasse von Element.

    Das Problem bei dir ist nicht erst das Template, sondern schon die (Forward-)Deklaration von Element::Base - deshalb taucht die Fehlermeldung des Compilers auch dort schon auf. Du kannst dich nicht auf Interna einer Klasse beziehen (d.h. auch nicht sie deklarieren), bevor du die Definition der Klasse begonnen hast.

    Wieweit ähneln sich die N Element-Klassen? Eventuell reicht es da, ein einzelnes Element-Klassentemplate zu definieren, wenn es nur um unterschiedliche Data-Klassen geht. Ggf. kann man kleinere Unterschiede im Verhalten auch durch Policies ausdrücken.



  • brotbernd schrieb:

    Das war keine Off-Topic Kritik, sondern ein Lösungsvorschlag, nämlich die Implementierung nicht über Vererbung sondern über Komposition zu benutzen. Dann kannst du deine Data Klasse auch in der Elementklasse definieren.
    Ansonsten bleibt dir nur -wie schon gesagt wurde- die Möglichkeit, die Data Klasse irgendwie vorher zu definieren.

    Komposition hilft nicht, da die austauschbaren Teile über ein gemeinsames Interface (das auch über Implementation verfügt) auch durch das Projekt auf Wanderschaft gehen. Das Interface und die identische Implementation soll daher vererbt werden.

    Ich will Code reduzieren, nicht vermehren.

    brotbernd schrieb:

    Nur interessehalber, wie groß ist so ein Stapel in 150MB denn etwa?

    150MB würde ich sagen... zumindest verstehe ich die Frage in etwa so, als ob Du fragst, wie hoch ein 50m hoher Turm in etwa ist.

    Stell dir einfach einen Block 20 Jahre lang gereifter Software vor.

    pumuckl schrieb:

    Das Problem bei dir ist nicht erst das Template, sondern schon die (Forward-)Deklaration von Element::Base - deshalb taucht die Fehlermeldung des Compilers auch dort schon auf. Du kannst dich nicht auf Interna einer Klasse beziehen (d.h. auch nicht sie deklarieren), bevor du die Definition der Klasse begonnen hast.

    Ich weiß... daher die Frage, wie ich diese Interna vordeklarieren kann. Ich weiß, dass Element eine Subklasse Data besitzt - der Compiler weiß es noch nicht. Wie kann ich ihm versichern, dass die Erklärung noch kommen wird?

    Ich brauche im Template ausschließlich Referenzen bzw. Pointer - von denen ist die Größe bekannt. Es geht "nur" um Typsicherheit.

    pumuckl schrieb:

    Wieweit ähneln sich die N Element-Klassen? Eventuell reicht es da, ein einzelnes Element-Klassentemplate zu definieren, wenn es nur um unterschiedliche Data-Klassen geht. Ggf. kann man kleinere Unterschiede im Verhalten auch durch Policies ausdrücken.

    Die Element-Klassen implementieren ein gemeinsames Interface. Sogesehen sind sich sehr ähnlich. Die eigentliche Implementation hingegen ist sehr unterschiedlich, entsprechend auch die Daten, die verwendet werden.

    Aber wie ich schon sagte - das Problem ist nicht, das Ganze lauffähig zu formulieren, sondern die einzelnen Data-Klassen in der jeweiligen Elementklasse zu verstecken.
    Ich suche das Gedicht, das rhetorisch Schöne. Das Zeug in Prosa darzubieten ist nicht mein Problem.



  • Xin schrieb:

    Stell dir einfach einen Block 20 Jahre lang gereifter Software vor.

    Sorry für OT:
    "20 Jahre gereift" klingt nach Wein. Den man 20 Jahre lang liegen gelassen hat.
    Mit der Analogie stelle ich mir die 20 Jahre gereifte Software lieber nicht vor - C++ von vor 20 Jahren ist ja nicht gerade das Höchste der Gefühle 😉



  • pumuckl schrieb:

    Xin schrieb:

    Stell dir einfach einen Block 20 Jahre lang gereifter Software vor.

    Sorry für OT:
    "20 Jahre gereift" klingt nach Wein. Den man 20 Jahre lang liegen gelassen hat.
    Mit der Analogie stelle ich mir die 20 Jahre gereifte Software lieber nicht vor - C++ von vor 20 Jahren ist ja nicht gerade das Höchste der Gefühle 😉

    Wäre das von vor 20 Jahren nur C++ gewesen....

    Lass Dir noch den Block auf der Zunge zergehen und fühle die leicht modrige Note von Fortran 77 im Abgang, während Dir ein wenig Visual Basic 6 im Halse kratzt und dafür sorgt, dass Dir ein Klumpen aus C# und Python in ebendiesem stecken bleibt.

    Das Design einer Software ist nach 20 Jahren ausgereift. Oder darüber hinaus. 😉
    Aber das heißt ja nicht, dass man es nicht aufbessern könnte.

    Der Begriff "Software-Archäologe" existiert tatsächlich - wusste ich vorher auch nicht. ^^

    </offtopic>



  • Xin schrieb:

    Lass Dir noch den Block auf der Zunge zergehen und fühle die leicht modrige Note von Fortran 77 im Abgang, während Dir ein wenig Visual Basic 6 im Halse kratzt und dafür sorgt, dass Dir ein Klumpen aus C# und Python in ebendiesem stecken bleibt.

    😋

    Das Design einer Software ist nach 20 Jahren ausgereift. Oder darüber hinaus. 😉

    Sagt die alte Schule 😉 Aktuell liest man häufiger die Meinung, dass das tatsächliche Design direkt im Code steckt und vom beabsichtigten Design mehr oder weniger abweicht (In der Theorie weniger, in der Praxis mehr). Wenn sich also der Code ändert, ändert sich das Design. Und das muss somit genau wie der Code gewartet und gepflegt werden. Nach der Meinung hat sich das was mit ausgereift 😉

    Aber bevor jetzt die nächste Glaubensdiskussion vom Gartenzaun bricht back to Topic: Wenn du uns noch ein wenig drumherum zu deinem beabsichtigten Code/Design gibst, finden wir vielleicht zusammen einen Weg, das in C++ schön (elegant, wartbar, ohne Code-Duplizierung,...) umzusetzen 🙂



  • pumuckl schrieb:

    Das Design einer Software ist nach 20 Jahren ausgereift. Oder darüber hinaus. 😉

    Sagt die alte Schule 😉

    Hier spricht man von "hysterisch gewachsenen Strukturen". 😉

    Aber es funktioniert.

    pumuckl schrieb:

    Aktuell liest man häufiger die Meinung, dass das tatsächliche Design direkt im Code steckt und vom beabsichtigten Design mehr oder weniger abweicht (In der Theorie weniger, in der Praxis mehr). Wenn sich also der Code ändert, ändert sich das Design. Und das muss somit genau wie der Code gewartet und gepflegt werden. Nach der Meinung hat sich das was mit ausgereift 😉

    Ich verwerfe ein Design, wenn ich sehe, dass es von meiner Absicht abweicht. Der Nachteil ist, dass Code so viel Pflege bedarf, aber ich bei meinen privaten Projekten Erweiterungen meist einfach dazustellen kann und die Sache läuft.

    pumuckl schrieb:

    Aber bevor jetzt die nächste Glaubensdiskussion vom Gartenzaun bricht back to Topic: Wenn du uns noch ein wenig drumherum zu deinem beabsichtigten Code/Design gibst, finden wir vielleicht zusammen einen Weg, das in C++ schön (elegant, wartbar, ohne Code-Duplizierung,...) umzusetzen 🙂

    Wie gesagt - das ist komplette System aufzuführen ist recht aufwändig und das eigentliche Problem - scheint mir in C++ so nicht formulierbar zu sein. Ich hatte noch ein paar weitere Ideen, die der VC++ auch nicht geschluckt hat, ich probiere das heute abend nochmal mit dem GCC zu Hause aus. VC++ ist was Templates angeht sowieso etwas schwachbrüstig.

    Da mich das hier aber nicht weiterbringt habe ich begonnen das Design zu ändern - damit erübrigt sich die Frage - für dieses Projekt jedenfalls.



  • Xin schrieb:

    Komposition hilft nicht, da die austauschbaren Teile über ein gemeinsames Interface (das auch über Implementation verfügt) auch durch das Projekt auf Wanderschaft gehen. Das Interface und die identische Implementation soll daher vererbt werden.

    Ich hab es zwar schon im ersten Post gesagt, will es aber nochmal erwähnen, weil es sich bei den Informationen und Aussagen die wir hier haben ein wenig so anhört als hättest du vielleicht eine falsche Vorstellung (vielleicht ist es auch nicht so).
    Da alle Elementklassen von einer anderen Basisklasse erben, nämlich Interface<MeinInterfaceUnterscheidetSichHierVonAllenAnderen> haben sie eben keine gemeinsame Schnittstelle. Sie haben alle eine Schnittstelle die einfach nur "zufällig" den gleichen Namen und gleiche Signatur hat. Dazu braucht man aber keine öffentliche Vererbung. Alles was du bekommst ist die Implementierung von bool func(DataInterface & bla) (macht die noch mehr als geprüfte Konvertierung zu DataType). Und für die Wiederverwendung einer Implementierung brauchst du keine Vererbung, mit der du hier ja ein offensichtliches Problem hast.

    Ach und die Frage nach dem Stapel war natürlich die Frage nach der Anzahl der Elementklassen (in Stück) 😉



  • brotbernd schrieb:

    Ich hab es zwar schon im ersten Post gesagt, will es aber nochmal erwähnen, weil es sich bei den Informationen und Aussagen die wir hier haben ein wenig so anhört als hättest du vielleicht eine falsche Vorstellung (vielleicht ist es auch nicht so).

    Es spricht grundsätzlich nichts dagegen, es in seine Überlegungen einzubeziehen, dass das Gegenüber falsche Vorstellungen der Programmierung hat. Ich programmiere jetzt seit 16 Jahren C++ und seit über 24 Jahren programmiere ich in anderen Sprachen. Ich mache das beruflich, habe bereits Schulungen für C++ gegeben und ich versichere Dir nochmals, dass ich hier ein syntaktisches Problem habe, das offenbar nicht lösbar ist und dass ich in der Lage bin eine Ableitung zu formulieren.

    Wenn ich in diesem Forum Anfragen stelle, so sind diese trivial und ich finde einfach den Schalter nicht oder sie sind nicht trivial, weil die Probleme nicht lösbar sind, bzw. ich beantworte sie dann oftmals selbst, weil sie nicht trivial sind.
    Ich muss aber sagen, dass bei nicht trivialen Sachen - unabhängig davon ob ich die Frage stelle oder eine Antwort gebe - in diesem Forum sich immer mindestens einer findet, der alles, was er für ungewöhnlich hält mit 'falsch' gleichsetzt und mir erklärt, dass ich nicht verstehe, was ich tue alternativ dass was ich tue sinnlos ist und dann auch interessanterweise nahezu grundsätzlich von "wir" sprechen. Diesbezüglich hatte ich schon sehr lange, sinnfreie Diskussionen hier und ich plane das nicht zu wiederholen.

    Das ist der Grund, warum ich in diesem Forum nur noch wenige Fragen stelle und in meinem Forum darauf achte, dass Formulierungen wie "wir hier haben ein wenig so anhört als hättest du vielleicht eine falsche Vorstellung (vielleicht ist es auch nicht so)" nicht aufkommen.

    Ich fragte nach einer möglichen Syntax, nicht nach einer Beratung für eine Designentscheidung. Dafür habe ich - absichtlich - zuwenig Details gegeben, als dass eine seriöse Beratung möglich wäre, um genau das zu vermeiden.

    brotbernd schrieb:

    Da alle Elementklassen von einer anderen Basisklasse erben, nämlich Interface<MeinInterfaceUnterscheidetSichHierVonAllenAnderen> haben sie eben keine gemeinsame Schnittstelle. Sie haben alle eine Schnittstelle die einfach nur "zufällig" den gleichen Namen und gleiche Signatur hat. Dazu braucht man aber keine öffentliche Vererbung.

    Sie haben eine gemeinsame Schnittstelle über die Basisklasse der Data-Varianten. Diese Methode versucht per dynamic_cast auf "DataType" zu casten und leitet das im Erfolgsfall an das jeweilige Element weiter. Die unterschiedlichen Elemente können so typsicher gleich mit ihrer erforderlichen Datenklasse arbeiten, ohne diese selbst noch downcasten zu müssen.

    brotbernd schrieb:

    Und für die Wiederverwendung einer Implementierung brauchst du keine Vererbung, mit der du hier ja ein offensichtliches Problem hast.

    Nein, ich habe ein deklaratives Problem, ich habe einen Typ, den C++ innerhalb eines Templates nicht kennen möchte. Die Vererbung funktioniert wunderbar.
    Was ich brauche oder nicht und womit ich offensichtlich Probleme habe, schrieb ich aber bereits im Eingangsposting.

    brotbernd schrieb:

    Ach und die Frage nach dem Stapel war natürlich die Frage nach der Anzahl der Elementklassen (in Stück) 😉

    Stetig wachsend.

    Deswegen soll die Implementierung ja auch so einheitlich wie möglich sein.



  • Der Thread war mir zwar eigentlich eindeutig zu pampig, aber mir ist gerade noch was eingefallen, wo mir das Wort mixin nochmal über den Weg lief. Vielleicht kannst du ja damit was anfangen:

    class StandardData
    {
    public:
        virtual ~StandardData(){}
        int a;
    };
    
    template<typename ElementT>
    class Interface
    {
    public:
        bool func(StandardData & bla)
        {
            typedef typename ElementT::Data* DataPtr;
            DataPtr d = dynamic_cast<DataPtr>(&bla);
            if (!d)
                return false;
            ElementT* el = static_cast<ElementT*>(this);
            el->funcImpl(*d);
            return true;
        }
    };
    
    class Element : public Interface<Element>
    {
    public:
       class Data : public StandardData
       {
         int b;
       };
    
       bool funcImpl(Data & myData)
       {
           std::cout << "Element func\n";
       }
    };
    
    class Element2 : public Interface<Element2>
    {
    public:
       class Data : public StandardData
       {
         int b;
       };
    
       bool funcImpl(Data & myData)
       {
           std::cout << "Element2 func\n";
       }
    };
    
    template<typename E, typename D>
    bool foo(E& e, D& d)
    {
        return e.func(d);
    }
    
    int main()
    {
        Element e;
        Element::Data d;
        Element2::Data d2;
        if (!foo(e, d))
            std::cout << "Da ist was schiefgegangen.";
        if (!foo(e, d2))
            std::cout << "Da ist was schiefgegangen.";
        return 0;
    }
    

    Ein Kommentar dazu gebe ich natürlich nicht ab, das ist hier ja unerwünscht 😉


Log in to reply