Spezialisierung von Template für Basisklassenzeiger



  • Hallo allerseits,

    ich programmiere normalerweise C#, muss aber für ein Projekt vorübergehend auf C++ zurückgreifen und kämpfe mit Templates.

    Folgendes ist mein Problem: Ich habe eine Klasse Object, die im Prinzip ein generischer Wrapper um andere Objekte ist. Für dieses Klassentemplate brauche ich zwei Spezialisierungen, eine für allgemeine Zeiger und eine für Zeiger auf Objekte vom Typ Basis und davon abgeleitete Klassen.

    Meine Definitionen sehen so aus:

    // allgemeines Template 
    template <typename T>
    class Object
    {
    ....
    }
    
    // Spezialisierung für Zeiger
    template <typename T>
    class Object<T*>
    {
    ....
    }
    
    // Spezialisierung für Basis* und abgeleitete Klassen
    // Führt zu Compilerfehler
    template <typename T>
    class Object<Basis*>
    {
    
    }
    

    Letzters führt zu einem Compilerfehler, da T nicht in der Parameterliste der partiellen Spezialisierung auftaucht. Klingt auch logisch, aber wie sieht die richtige Syntax dafür aus?

    Ich könnte natürlich das Template vollständig für Basis* spezialisieren, aber dadurch würde mir Typsicherheit verloren gehen und ich wäre auf Typecasts angewiesen.

    Im Prinzip möchte ich in der Lage sein, meine Templates in etwa so zu nutzen.

    Object<Basis> obj;        // Nimm das erste Template
    Object<int*> obj;         // Nimm das zweite Template
    Object<Abgeleitet*> obj;  // Nimm das dritte Template
    

    Wie mache ich das am besten?



  • der_alex1980 schrieb:

    ich programmiere normalerweise C#, muss aber für ein Projekt vorübergehend auf C++ zurückgreifen und kämpfe mit Templates.

    Ok, dann eins vorweg: Möglicherweise gibt es für das, was Du eigentlich machen willst, noch einen eleganteren Ansatz.

    der_alex1980 schrieb:

    Im Prinzip möchte ich in der Lage sein, meine Templates in etwa so zu nutzen.

    Object<Basis> obj;        // Nimm das erste Template
    Object<int*> obj;         // Nimm das zweite Template
    Object<Abgeleitet*> obj;  // Nimm das dritte Template
    

    Wie mache ich das am besten?

    Aha, Du suchst also nach einer "bedingten Teil-Spezialisierung" für T*, wobei die Bedingung IsBaseOf<Base,T> ist.

    Gegenfrage: Ist das überhaupt sinnvoll in Deinem Fall? Warum willst Du überhaupt drei Spezialisierungen? Wo sollen denn da die Unterschiede sein?

    Ja, man kriegt so etwas hin in C++. Aber ich befürchte, dass das, was Du eigentlich machen willst, anders noch eleganter geht.

    Sonst, guckst Du mal bei
    http://www.boost.org/doc/libs/1_46_1/libs/utility/enable_if.html
    und
    http://www.boost.org/doc/libs/1_46_1/libs/type_traits/doc/html/index.html
    nach.

    kk



  • Meine Codebeispiele sind stark vereinfacht und dienen nur der prinzipiellen Veranschaulichung. In Wirklichkeit haben die Templates noch eine gemeinsame Basisklasse, die aber kein Template ist.

    Ok, dann eins vorweg: Möglicherweise gibt es für das, was Du eigentlich machen willst, noch einen eleganteren Ansatz.

    Nagut, ich versuche mal zu erklären, was ich möchte. Ich habe ein C# Projekt, bei dem ich auf eine QT basierte C++ Bibliothek zugreifen möchte und das ganze auch noch unter Linux. C# und C++ sind zwar beides objektorientierte Sprachen, allerdings können sie (zumindest auf Linux) nicht direkt zusammenarbeiten, sondern der Zugriff muss über eine C-kompatible Wrapper Bibliothek erfolgen und diese schreibe ich gerade. C-ḱompatibel heißt, dass die Bibliothek zwar mit g++ kompiliert wird und auch alle Sprachmittel von C++ zur Verfügung stehen, allerdings sind alle Funktionen, welche von C# benötigt werden als extern "C" definiert.

    Komplexe Datentypen wie Klassen können zwischen C# und dem Wrapper nur als Pointer übergeben werden. Da C# einen Garbage Collector besitzt und C++ nicht, muss man besonderes Augenmerk auf die Speicherverwaltung legen. Um diese so einfach wie möglich zu halten, möchte ich jedes C++ Objekt, das ich von C# erzeuge oder das als Werttyp (sprich Kopie) von einer C++ Methode zurückgegeben wurde in ein Wrapper Objekt packen, das dann von einer einzigen Funktion zerstört werden kann. Ich übergebe zwischen C# und C++ also nur Pointer auf Wrapper.

    So in etwa sieht das ganze in Wirklichkeit aus. Object ist die Wrapper Klasse und sie soll beliebige Datentypen beinhalten können.

    class ObjectBase
    {
        public:
            virtual ~ObjectBase() {}
    
    };
    
    template <typename T>
    class Object : public ObjectBase
    {
       private:
        T _instance;
    
        public:
           Object(T& obj) 
           { 
              _instance = obj; 
           }
    
           T* instance() 
           { 
              return &_instance; 
           }
    }; 
    
    // Spezialisierung für Zeiger
    template <typename T>
    class Object<T*> : public ObjectBase
    {
       protected:
          T* _instance;
    
       public:
           Object(T* obj) 
           { 
              _instance = obj; 
           }
    
           T* instance() 
           { 
              return _instance; 
           }
    
           ~Object() 
           {
              if ( _instance != 0 )
                 delete _instance;
           }
    }
    
    extern "C" void destroy_object(ObjectBase* obj)
    {
       if (obj != 0)
          delete obj;
    }
    

    Das obere Object besitzt keinen Destruktor, da Nicht-Zeiger automatisch zerstört werden.
    Wenn also das entsprechende C# Pedant des Objekts vom Garbage Collector eingesammelt wird, ruft dieser im Finalizer destroy_object() auf, so dass das Objekt auch auf C++ Seite zerstört wird.

    Nun ist es aber so, dass QT eine Klasse QObject definiert, welche die Basisklasse für viele aber nicht alle QT Objekte ist. Jedes QObject kann beliebig viele Kindobjekte enthalten und wenn ein QObject zerstört wird, werden alle Kindobjekte mitzerstört.

    Auf C# Seite bekomme ich aber nicht mit, ob ein QObject schon von seinem Elternobjekt zerstört wurde, da die Reihenfolge, mit der der Garbage Collector Objekte zerstört, nicht festgelegt ist. Das heißt, es besteht die Möglchkeit, dass destroy_object() mit einem Objekt aufgerufen wird, dessen _instance nicht mehr auf einen gültigen Speicherbereich zeigt und delete dann einen Fehler verursacht. Ich versuche zwar solche Szenarien schon auf C# Seite zu minimieren, aber ganz vermeiden lässt sich das glaube ich nicht.

    Glücklicherweise gibts in QT eine QPointer Klasse, die so etwas wie einen Guarded Pointer bereitstellt, das heißt wenn ein QObjekt irgendwo zerstört wird, werden alle Pointer auf dieses Objekt auf 0 gesetzt. Dieser Guarded Pointer funktioniert aber nur mit QObject und davon abgeleiteten Klassen und genau für diese Art Klassen wollte ich mein drittes Template spezialiseren und innerhalb des Templates einen QPointer anstelle eines normalen Pointers verwenden.



  • Du musst vor dem delete nicht prüfen, ob es sich um einen Nullzeiger handelt. delete auf Nullzeiger ist erlaubt und tut nichts.

    Du willst also eine Spezialisierung für QPointer einrichten?

    template <class QObjectDerivate>
    class Object< QPointer<QObjectDerivate> >;
    

    Oder soll der Template-Parameter nur die Klasse selbst sein, ohne QPointer ? Dann könntest du mit TypeTraits aus Boost oder TR1, genauer gesagt mit is_base_of schauen, ob die Klasse von QObject erbt. Entweder wird das Template mit SFINAE ( enable_if ) ausgewählt, oder du brauchst sonst eine Art von Metaprogrammierung.



  • Nexus schrieb:

    Du musst vor dem delete nicht prüfen, ob es sich um einen Nullzeiger handelt. delete auf Nullzeiger ist erlaubt und tut nichts.

    Danke, das wusste ich noch nicht.

    Du willst also eine Spezialisierung für QPointer einrichten?

    template <class QObjectDerivate>
    class Object< QPointer<QObjectDerivate> >;
    

    Oder soll der Template-Parameter nur die Klasse selbst sein, ohne QPointer ?

    Der Parameter soll nur die Klasse selbst sein, also z.B. <QObject*> oder <QWidget*>. Ich denke, ich werde mich mal in die Untiefen der Metaprogrammierung einarbeiten und mich mit TypeTraits auseinandersetzen. 😉

    Dann könntest du mit TypeTraits aus Boost oder TR1, genauer gesagt mit is_base_of schauen, ob die Klasse von QObject erbt. Entweder wird das Template mit SFINAE ( enable_if ) ausgewählt, oder du brauchst sonst eine Art von Metaprogrammierung.



  • Generell solltest Du versuchen, Spezialisierungen von "größeren" Klassen zu vermeiden und stattdessen Unterschiede auszugliedern. Du kriegst es auch ganz ohne Spezialisierungen des Object-Templates hin:

    template<class T, bool UseQP>
    struct select_ptr_type { typedef T* type; };
    template<class T>
    struct select_ptr_type<T,true> { typedef QPointer<T> type; };
    
    template<class T>
    struct stored_type { typedef T type; }
    template<class T>
    struct stored_type<T*>
      : select_ptr_type<T,
          boost::is_base_of<QObject,T>::value > {};
    
    template<class T> inline void destroy(T const&) {} // do nothing
    template<class T> inline void destroy(T *ptr) {delete ptr;}
    template<class T> inline void destroy(QPointer<T> qp) {...}
    
    template<class T>
    class Object : public ObjectBase
    {
       typedef typename stored_type<T>::type st;
       st instance_;
    
       // Abschalten der Kopieroperationen
       // (aus Sicherheitsgründen)
       Object(Object const&); // wird nicht definiert
       Object& operator=(Object const&);  // auch nicht
    public:
       Object(T const& x) : instance_(x) {}
       ~Object() {destroy(instance_);}
    };
    

    Ein bissel TMP (template metaprogramming) und ein bissel Overloading.
    (ungetestet)

    Die ObjectBase Klasse würde ich auch noch abstract machen, damit Slicing unterbunden wird.

    kk



  • Vielen dank, das ist genau was ich wollte! Und das beste ist, ich versteh sogar den Code. 🙂


Anmelden zum Antworten