Interface für generischen Typ



  • knivil schrieb:

    void*

    Und wie überprüfst du dann, um welchen Typen dass es sich handelt? Da hast du doch sämtliche Informationen verloren...



  • In deinen Container speichert du Zeiger auf eine "generische" Basisklasse, also so etwas wie einen void-Zeiger. Dann kannst du davon für jeden beliebigen Typen eine Ableitung bilden.
    Für eine Optionsliste, würden sich auch einfache Strings anbieten. Zum Speichern, bzw. Einlesen sind sie quasi notwendig, also ist auch eine Konvertierung zwingend. Beim Zugriff kannst du dann den String in einen konkreten Typen umwandeln lassen.



  • ccquestions schrieb:

    Hallo zusammen

    Ich hab mir im Zusammenhang mit dem Thread "sind templates von template-spezialisierungen möglich?" überlegt, wie man ein Interface für eine Klasse bauen kann, die selbst Objekte verschiedenen Typs verwaltet und wie man dann darauf zugreifen könnte.

    Sinnvoll geht das nur mit einer Root Class von der alle zuspeichernden Klassen abgeleitet sind. void* hat so seine Nachteile. C++ ist allerdings für ein solches Vorgehen mangels Unterstützung im Laufzeitsystem (es gibt nicht die eine Root Class) nicht so gut geeignet. Trotzdem kann man das durch Disziplin erreichen.





  • Ich habe keine Information bei void* verloren, das Objekt ist genauso vorhanden wie vorher. Ich kann mit dynamic_cast auf den gesuchten Typen casten und das Ergebnis pruefen. Nichts anderes wird boost::any machen, es kapselt void* einfach nur.



  • knivil schrieb:

    Ich habe keine Information bei void* verloren, das Objekt ist genauso vorhanden wie vorher. Ich kann mit dynamic_cast auf den gesuchten Typen casten und das Ergebnis pruefen.

    Wirklich? Soviel mir bekannt ist, arbeitet dynamic_cast auf RTTI, void* fehlen aber jegliche Typinformation. Ich würde hier eher ein reinterpret_cast erwarten.



  • asc schrieb:

    knivil schrieb:

    Ich habe keine Information bei void* verloren, das Objekt ist genauso vorhanden wie vorher. Ich kann mit dynamic_cast auf den gesuchten Typen casten und das Ergebnis pruefen.

    Wirklich? Soviel mir bekannt ist, arbeitet dynamic_cast auf RTTI, void* fehlen aber jegliche Typinformation. Ich würde hier eher ein reinterpret_cast erwarten.

    Lustig. Ich einen static_cast 🙂

    Die korrekte Antwort hat pumuckl ja schon gesagt: boost::any (bzw etwas äquivalentes)



  • knivil schrieb:

    Ich habe keine Information bei void* verloren, das Objekt ist genauso vorhanden wie vorher. Ich kann mit dynamic_cast auf den gesuchten Typen casten und das Ergebnis pruefen. Nichts anderes wird boost::any machen, es kapselt void* einfach nur.

    Dass dynamic_cast so nicht funktioniert wurde ja schon geschrieben (void erfüllt die Voraussetzungen für einen dynamic_cast Source Typ nicht).

    Eine Möglichkeit sowas wie boost::any zu implementieren ist Funktionszeiger zu verwenden um die Typinformation zu erhalten. D.h. du machst ein Funktionstemplate, das den "any_cast" für "seinen" Typ implementiert, und "seinen" Typ als void* entgegennimmt.

    Bei der Zuweisung (wo der Typ ja noch bekannt ist), holst du dir den Zeiger auf die passende Spezialisierung.

    template <class T>
    void DoSomething(void* obj)
    {
        static_cast<T*>(obj)->DoSomething();
    }
    
    template <class T>
    void DeleteIt(void* obj)
    {
        delete static_cast<T*>(obj);
    }
    
    template <class T>
    void* CloneIt(void const* obj)
    {
        return new T(*static_cast<T const*>(obj));
    }
    
    class Foo
    {
    public:
        template <class T>
        explicit Foo(T t)
            : m_obj(0),
            m_doSomething(&DoSomething<T>),
            m_deleter(&DeleteIt<T>),
            m_cloner(&CloneIt<T>)
        {
            m_obj = new T(t);
        }
    
        ~Foo()
        {
            m_deleter(m_obj);
        }
    
        void DoSomething()
        {
            m_doSomething(m_obj);
        }
    
        Foo(Foo const&);
        Foo& operator = (Foo const&);
    
        // ...
    
    private:
        void* m_obj;
        void (*m_doSomething)(void*);
        void (*m_deleter)(void*);
        void* (*m_cloner)(void const*);
    };
    

    Die ganzen Zeiger kann man natürlich in einer statischen Instanz einer Templateklasse sammeln.

    Oder man verwendet gleich virtuelle Funktionen. Also (non-template) Basisklasse mit den virtuellen Funktionen, Klassen-Template welches diese implementiert und eine statische Instanz von sich selbst enthält. Auf diese statische Instanz holt man sich den Zeiger (als Basisklassen-Zeiger), und über den ruft man die virtuellen Funktionen auf.

    Im Prinzip alles das selbe, Variationen in Form und Farbe sozusagen.

    EDIT: Fehler im Code beseitigt /EDIT



  • Man kann void* nicht in dynamic_cast werfen, aber man kann sich beispielsweise das std::type_info-Objekt bei der Zuweisung merken. Mal ganz simpel gestrickt:

    #include <iostream>
    #include <stdexcept>
    #include <typeinfo>
    
    class rtti_holder {
    public:
      template<typename T>
      rtti_holder(T &ref) : ptr_(&ref), tinfo_(typeid(ref)) { }
    
      template<typename T>
      T &as() {
        if(tinfo_ == typeid(T)) {
          return *static_cast<T*>(ptr_);
        }
    
        throw std::logic_error("Falscher Typ");
      }
    
    private:
      void *ptr_;
      std::type_info const &tinfo_;
    };
    
    int main() {
      int i = 2;
    
      rtti_holder r(i);
    
      std::cout << r.as<int >() << std::endl; // Funktioniert
      std::cout << r.as<char>() << std::endl; // Wirft Exception
    }
    

    Wobei natürlich Besitzansprüche gesondert zu regeln sind und im Zweifel auf etwas hinauslaufen, wie es hustbaer vorschlägt.



  • Type Erasure ist da meiner Meinung nach schöner.

    class any
    {
        struct any_impl_base
        {
            virtual ~any_impl_base() {}
        };
    
        template <typename T>
        struct any_impl : any_impl_base
        {
            T object;
            any_impl(T obj) : object(obj) {}
        };
    
        any_impl_base* impl;
    
    public:
    
        template <typename T>
        any(T obj) : impl(new any_impl<T>(obj)) {}
    
        ~any() { delete impl; }
    
        template <typename T>
        T& get() { return dynamic_cast<any_impl<T>&>(*impl).object; }
    
        template <typename T>
        any& operator = (T obj)
        {
            any_impl<T>* ptr = new any_impl<T>(obj);
            delete impl;
            impl = ptr;
            return *this;
        }
    };
    
    template <typename T>
    T& any_cast(any& a)
    {
        return a.get<T>();
    }
    

    Fehlen natürlich noch einige Sachen, aber vom Prinzip her würde ich das so lösen.



  • 314159265358979 schrieb:

    Type Erasure ist da meiner Meinung nach schöner.

    Schöner als was?
    Sowohl dein Beispiel als auch meins als auch das von seldon machen Type Erasure.



  • void* hat nicht viel mit Type Erasure zu tun, imo.



  • 314159265358979 schrieb:

    void* hat nicht viel mit Type Erasure zu tun, imo.

    In Java ist das der Standard Weg. Da macht man überall void* wie in seldon Besipiel und nennt sie halt Object-Referenz.

    Wieso sollte es also in C++ keine type erasure mehr sein.

    (unabhängig jetzt davon welche implementierung hier die schönste ist)



  • Pis Weg löst das Problem mit Besitzverhältnissen recht elegant, wenn man Kopiersemantik braucht - in diesem Fall muss sowieso an den Heap gegangen werden. Braucht man dagegen Referenzsemantik, scheint mir mein Weg sinnvoller - der virtuelle Destruktor bietet dann keine Vorteile, und man spart sich den Overhead der dynamischen Speicherallokation.

    Ungeachtet dessen sehe ich eine gewisse Ironie darin, im Zusammenhang mit dieser Anforderung über die Schönheit der Implementation zu sprechen. Allein die Tatsache, dass man etwas derartiges benötigt, deutet auf wichtigere Designprobleme hin.



  • Vielen Dank, hustbaer, seldon & 314159265358979, eure Antworten haben mich weiter gebracht! Kannte Type Erasure nicht bis anhin.

    Wie ist das mit Runtime Type Information in Bezug auf typeid respektive dynamic_cast? Ich habe mich da bis jetzt nicht weiter damit beschäftigt, soweit ich weiss wird aber der Compiler bei beiden Varianten RTTI brauchen, also überall im Code, auch wo gar nicht benötigt zusätzlichen Code einfügen, was dann tendenziell die Performance ziemlich beeinträchtigen kann, typeid noch weniger als dynamic_cast?

    Wärs da nicht effizienter, da ich die Typeninformationen nur hier lokal benötige im Container für den generischen Typ in den vom Compiler erstellten Templateklassen, diese selber zu implementieren? Also etwas in der Art wie:

    class TypeInfo
    {
     public:
       TypeInfo():Number(++OverallNumber){};
    
       const int id() const{ return Number; };
     private:
       const int Number;
       static int OverallNumber;
    };
    
    int TypeInfo::OverallNumber=0;
    
    class GenericType
    {
      class Object
      {
        public:
          virtual const int getTypeId() const =0;
      };
    
      template<class T>
      class ObjectInstance:public Object
      {
        public:
          ObjectInstance( T _value ):Value(_value),TypeId(*ClassTypeId){};
    
          T Value;
    
          const int getTypeId() const { return TypeId.id();};
          static const int typeId(){ return ClassTypeId->id();};
        private:
          const TypeInfo TypeId;
          static const TypeInfo* const ClassTypeId;
      };
    
      public:
        template<class T>
        GenericType( T _value ){
          Content=new ObjectInstance<T>( _value );
        };
        ~GenericType() 
        { 
          delete Content; 
        };
        GenericType( const GenericType& _toCopy ){ /*...*/};
    
        template<class T>
        T& as()
        {
          if( GenericType::ObjectInstance<T>::typeId()==Content->getTypeId() )
          {
            return static_cast< ObjectInstance<T>* >(Content)->Value;
          }
          // else throw an exception
        };
    
      private:
        Object* Content;
    };
    
    template<class T>
    const TypeInfo* const GenericType::ObjectInstance<T>::ClassTypeId=new TypeInfo();
    

    ?

    seldon schrieb:

    Ungeachtet dessen sehe ich eine gewisse Ironie darin, im Zusammenhang mit dieser Anforderung über die Schönheit der Implementation zu sprechen. Allein die Tatsache, dass man etwas derartiges benötigt, deutet auf wichtigere Designprobleme hin.

    Inwiefern? Scheint mir die optimale Methode um veränderbare Settings, die ganz natürlicherweise unterschiedliche Typen haben können in einer gemeinsamen Klasse mit identischem Zugriff speichern zu können?
    Btw. Gäbs boost::any wohl kaum, wenn sowas nur benötigt wird, wenn beim Design was schief gelaufen ist..



  • 314159265358979 schrieb:

    void* hat nicht viel mit Type Erasure zu tun, imo.

    Wieso nicht?
    void* ist IMO die ultimative Type-Erasure, viel weniger "Typ" als void* kann man wohl nicht haben 😉



  • @ccquestions:
    Die Performance spielt bei dem was du da machen willst IMO keine grosse Rolle.
    Ich würde dafür auf jeden Fall keinen Type-Info Code selbst schreiben.



  • hustbaer schrieb:

    @ccquestions:
    Die Performance spielt bei dem was du da machen willst IMO keine grosse Rolle.

    Das ist auch die Frage bezüglich des RTTI: Ich weis nicht wie das genau umgesetzt wird vom Compiler etc. Hab ich da nicht, sobald irgendwo mal der typeid header auftaucht im ganzen Programm das Runtime Type Tracing drin, was ja dann völlig idiotisch wäre, da ichs ja wirklich nur an der Stelle da benötige, notabene mit Typen, die bei der Kompilierung ja eigentlich dem Compiler sogar bekannt sind?

    hustbaer schrieb:

    Ich würde dafür auf jeden Fall keinen Type-Info Code selbst schreiben.

    Wenn der typeid der std library das schön umsetzt ja klar, auf der anderen Seite reicht mir hier ja eine simple Integer Id ohne das ganze Getöse rundherum völlig aus, also so wie im Code oben funktioniert das perfekt...



  • ccquestions schrieb:

    Hab ich da nicht, sobald irgendwo mal der typeid header auftaucht im ganzen Programm das Runtime Type Tracing drin, was ja dann völlig idiotisch wäre, da ichs ja wirklich nur an der Stelle da benötige, notabene mit Typen, die bei der Kompilierung ja eigentlich dem Compiler sogar bekannt sind?

    Runtime type information. Das ist ohnehin standardmässig aktiviert (wegen Exceptions) und normalerweise kein Problem.

    ccquestions schrieb:

    auf der anderen Seite reicht mir hier ja eine simple Integer Id ohne das ganze Getöse rundherum völlig aus, also so wie im Code oben funktioniert das perfekt...

    Warum selbst basteln, warten und konsistent halten? Hast du Angst vor dem Overhead?



  • Nexus schrieb:

    Runtime type information.

    Weiss ich danke. Mir gehts nur aufn Sack immer die gleichen Wörter zu verwenden 😉 Grundsatz Nr. 1 beim Schreiben 😃

    Nexus schrieb:

    Das ist ohnehin standardmässig aktiviert (wegen Exceptions) und normalerweise kein Problem.

    Genau hierauf bezog sich die Frage. Das bedeutet also, dass dynamic_cast nur zu Overhead führen kann, weil dann bei Einsatz die ganzen Vererbungen etc durchgegangen werden müssen? Die Informationen sind sowieso alle schon da?

    Nexus schrieb:

    Warum selbst basteln, warten und konsistent halten? Hast du Angst vor dem Overhead?

    Wenn die Informationen sowieso da sind, ist dies natürlich unnötig.


Anmelden zum Antworten