Vorwärtsdeklaration verschachtelter Typen



  • Du kannst innerhalb der Klasse eine forward declaration von nested classes machen, aber nicht von ausserhalb 😉

    Es gab aber glaube ich ein Proposal für C++0x für dieses Feature^^



  • Nexus schrieb:

    Bist du sicher, dass du "MyClass.hpp" nicht inkludiert hast?

    Habe nur C/P gemacht. 😉 - Nein, demfall scheint das nicht zu gehen..

    Das einzige, was geht ist die reine Vorwärtsdeklaration der nested Klasse. Dann müsstest du da halt einen Wrapper schreiben, der dir dann den Rest von MyClass ebenfalls durch diese Methode enthält.

    class MyClass
    {
     public:
     struct MyProxy;
    ...
    };
    ...
    
    struct MyClass::MyProxy 
    {
     ...
    };
    


  • drakon schrieb:

    Habe nur C/P gemacht. 😉

    Dabei hab ich extra fette ===-Trennlinien gemacht. Vielleicht wären zwei Felder besser gewesen... 😉

    drakon schrieb:

    Dann müsstest du da halt einen Wrapper schreiben, der dir dann den Rest von MyClass ebenfalls durch diese Methode enthält.

    Da verstehe ich nicht, wie du das meinst. Damit innerhalb des Wrappers der Typ MyClass bekannt ist, muss man ja dessen Header auch inkludieren.

    Mir fällt gerade keine typsichere Lösung ein. Aber den Header zu inkludieren finde ich auch nicht gut.



  • Hmm. Wenn du das so machen willst, kommst du anscheinen um einen cast nicht herum..



  • Also was ich gemeint habe:

    class MyClass;
    
    //Das wird halt includede, ist aber nicht so tratisch
    class MyWrapperClass
    {
    public:
    	struct MyProxy;
    private:
    	MyClass* body;
    };
    
    struct MyWrapperClass::MyProxy;
    
    class OtherClass
    {
        MyWrapperClass::MyProxy* Ptr;
        void Function(const MyWrapperClass::MyProxy&);
    };
    

    Ist nicht mehr ganz das gleiche und die Verbindung direkt zu MyClass ist auch nicht da, aber imo ist es auch fragwürdig eine public nested Class zu haben.. Warum ist die nicht eh schon ganz unabhängig? - Ein namespace würde es wahrscheinlich ja auch tun. Und dann hast du praktisch das, was ich da gemacht habe..



  • Die Nested-Struktur ist nicht unabhängig, weil sie logisch zur äusseren Klasse gehört und Attributtypen der Klasse definiert. Ich finde es nicht gut, alles global zu haben, wenn es nur im Zusammenhang mit der Klasse gebraucht wird.

    Ich hab jetzt noch ein wenig mit Templates rumprobiert. Dabei hat mich wieder eine Gegebenheit, ab der ich mich schon mal geärgert habe, aufgeregt: Dass Templateargumente nicht ersetzt werden, wenn sie als Member gekennezeichnet sind. Was soll das bringen? Wenn ich innerhalb eines Templates einen Bezeichner verwende, der mit einem Templateparameter übereinstimmt, dann soll der auch ersetzt werden! 🙄

    Ich war bei so etwas:

    template <typename Outer, typename Inner>
    struct NestedHacker
    {
        typedef typename Outer::Inner Type; // geht nicht, weil Inner nicht ersetzt wird.
    };
    

    Also müsste ich es mit Makros lösen und hätte am Schluss so viele Indirektionen, dass es mir zu blöd wird. Naja, ich werde es wohl mit void* tun müssen...



  • Wie sollte man Inner uebergeben? Inner muss ja ein korrekter Typ sein...
    ein

    NestedHacker<vector<int>, iterator> n;
    

    kann ja nie kompilieren...

    Textersetzung macht man mit #define

    wenn Inner nur ein dummy ist, kann man aber wrappen:

    struct iterator {} ; //dummy
    struct pointer {} ; //dummy
    
    template<typename Base, typename Inner>
    struct inner_wrapper {};
    
    template<typename Outer>
    struct inner_wrapper<Outer, iterator> {
      typedef Outer::iterator type;
    };
    template<typename Outer>
    struct inner_wrapper<Outer, pointer> {
      typedef Outer::pointer type;
    };
    
    template <typename Outer, typename Inner>
    struct NestedHacker
    {
        typedef inner_wrapper<Outer, Inner>::type type;
    };
    


  • Du hast Recht, ich habe dabei gar nicht mehr an das ursprüngliche Konzept der Templates gedacht. Eigentlich klar, dass das nicht ersetzt wird. Danke für deinen Vorschlag.

    Das hilft mir allerdings nicht sehr viel, da die Nested-Typen nicht nur Dummys sind, sondern Informationen tragen. Momentan hab ichs deshalb mit void* gelöst.

    /rant/ erwähnte etwas wegen neuem Standard, weiss jemand mehr darüber?



  • Auch wenn mir nicht einleuchtet warums unbedingt ein innerer Typ sein muss (auch äußere Hilfsklassen können mit zum Interface gezählt werden, genauso wie z.B. freie Operatorfunktionen etc.), folgendes in Kürze:

    //==== NestedFwdDecl.hpp ====
    template <class T, size_t N = 1>
    struct NestedFwdDecl : public NestedFwdDecl<O, N-1> //iterierende Vererbung
    {
       class Inner;
    };
    
    template <class T> struct NestedFwdDecl<T, 0> {}; //Ende der Iteration
    
    //==== OtherClass.hpp ====
    #include "NestedFwdDecl.hpp"
    
    class Outer;
    
    class OtherClass
    {
      typename NestedFwdDecl<Outer, 1>::Inner* iptr1; //Benutzen der forwarddeklarationen
      void foo(typename NestedFwdDecl<Outer, 2>::Inner& iref2);
    };
    
    //==== Outer.hpp ====
    #include "NestedFwdDecl.hpp"
    class Outer : public NestedFwdDecl<Outer, 2> //CRTP, damit andere Klassen andere innere Typen ableiten können.
    {
      typedef typename NestedFwdDecl<Outer, 1>::Inner Inner1;
      typedef typename NestedFwdDecl<Outer, 2>::Inner Inner2;
    };
    
    //==== Outer.cpp ====
    #include "Outer.hpp"
    class NestedFwdDecl<Outer, 1>::Inner { /* Definition von Inner1 */};
    class NestedFwdDecl<Outer, 2>::Inner { /* Definition von Inner2 */};
    

    Zu beachten ist, dass im Beispiel das Template mit allen möglichen Werten für N instantiiert werden kann, ebenso kann z.B. class NestedFwdDecl<Outer, 100>::Inner definiert werden. Outer erbt allerdings nur von den ersten beiden NestedFwdDecl-Klassen (und von der dummy-Nullklasse), die anderen Klassen wären also keine inneren Klassen von Outer mehr. Bei Compilern mit EBO bringt die Vererbung keinerlei Größenzuwachs mit sich. Man könnte natürlich die lange vererbungshierarchie in eine breite Hierarchie verwandeln indem Outer von N einzelnen structs erbt, allerdings greift bei vielen Compilern die EBO nicht mehr, wenn sie mit Mehrfachvererbung zu tun haben.



  • Zum Vereinfachen noch zwei Makros dazu:

    //==== NestedFwdDecl.hpp ====
    template <class T, size_t N = 1>
    struct NestedFwdDecl : public NestedFwdDecl<O, N-1> //iterierende Vererbung
    {
       class Inner;
    };
    
    template <class T> struct NestedFwdDecl<T, 0> {}; //Ende der Iteration
    
    #define NESTED_TYPEDEF(outer, n, name) typedef NestedClassFwdDecl<outer,n>::Inner name
    #define NESTED_CLASS_DEF(outer, n) template<> class NestedClassFwdDecl<outer,n>::Inner
    
    //==== OtherClass.hpp ====
    #include "NestedFwdDecl.hpp"
    
    class Outer;
    
    class OtherClass
    {
      NESTED_TYPEDEF(Outer, 1, Proxy);
      Proxy* proxptr1; //Benutzen der forwarddeklarationen
    public:
      void foo(NestedFwdDecl<Outer, 2>::Inner& iref2); //ohne typedef
    };
    
    //==== Outer.hpp ====
    #include "NestedFwdDecl.hpp"
    class Outer : public NestedFwdDecl<Outer, 2> 
    {
      NESTED_TYPEDEF(Outer, 1, Inner1); //typedeft Outer::Inner1
      NESTED_TYPEDEF(Outer, 2, Inner2);
    };
    
    //==== Outer.cpp ====
    #include "Outer.hpp"
    NESTED_CLASS_DEF(Outer, 1)
    { 
      /* Definition von Inner1 */
    };
    NESTED_CLASS_DEF(Outer, 2)
    { 
      /* Definition von Inner2 */
    };
    


  • Wow, vielen Dank für deine Mühe, pumuckl. 👍

    Hast du das jetzt einfach so schnell aus dem Ärmel geschüttelt? Damit ist man ja richtig flexibel. Dachte ich doch, dass es auch ohne void* sauber geht. Irgendwie schon interessant, mit Templates und Präprozessor kann man sich für alles Workarounds basteln. 😉

    pumuckl schrieb:

    Auch wenn mir nicht einleuchtet warums unbedingt ein innerer Typ sein muss (auch äußere Hilfsklassen können mit zum Interface gezählt werden, genauso wie z.B. freie Operatorfunktionen etc.)

    Ich hatte zuerst auch eine freie Struktur, allerdings hat mein Projekt inzwischen so viele Klassen, dass ich diese ein wenig gliedern will. Da ich dies innerhalb des Projektes nicht mit Namensräumen mache, habe ich die Struktur in die logisch übergeordnete Klasse gepackt. Diese Klasse besitzt auch gleich ein Member vom Typ der Struktur (die Struktur fasst also gewisse Eigenschaften der Klasse zusammen).



  • Nexus schrieb:

    Hast du das jetzt einfach so schnell aus dem Ärmel geschüttelt?

    naja ganz so schnell wars nicht, die Mittagspause nicht mitgerechnet wars ca. ne halbe Stunde, vielleicht auch 40 min.

    Nexus schrieb:

    Irgendwie schon interessant, mit Templates und Präprozessor kann man sich für alles Workarounds basteln. 😉

    Wenn man Modern C++ und boost-Doku liest und beizeiten mal in die verschiedenen boost-header spickt wird man zu sowas inspiriert 😉

    Was mir an der Lösung noch nicht gefällt ist dass man problemlos Pointer auf innere Klassen deklarieren kann die es garnicht gibt (z.B. mit N>2 im Beispiel).
    Man kann diese inneren Klassen sogar später definieren ohne dass einem der Compiler auf die Finger haut. Selbst die Ableitung von Outer vom Template darf man vergessen ohne dass es größere Probleme gibt - das einzige was die Sache beisammenhält ist die Vererbungsbeziehung. Das zu fixen wäre aber deutlich mehr Aufwand. Vor allem die Pointer auf nicht vorhandene innere Klassen kann man an ihrem Deklarationsort mit Sicherheit nicht detektieren, schließlich ist es ja Sinn des Ganzen, Pointer/Referenzen auf noch nicht definierte Klassen deklarieren zu können. Lediglich beim späteren Hantieren mit den Pointern und Referenzen wird man das merken weil die umgebenden Klassen eben nicht in der Hierarchie von Outer sind.
    Die Definition von ungültigen inneren Klassen könnte man evtl. verhindern, indem man ins NESTED_CLASS_DEF-Makro noch einen entsprechenden Compiletime-check einbaut, dazu muss die Klassendefinition von Outer allerdings davor geschehen.


Anmelden zum Antworten