Properties Fragen



  • Hi,

    Ich häng fest 😕
    Ich möchte meine Klassen um Properties erweitern. Einen ersten Ansatz hab ich schon gefunden, nur ist der recht unflexibel.
    Es werden Zeiger auf die getter und setter-Funktionen der Klasse gespeichert.
    Für elementare Typen schaut das ja immer so aus:

    int Klasse::value() const;
    void Klasse::setValue( int val );
    

    Für nicht-Elementare Sachen arbeitet man aber meistens mit const-Referenzen:

    const std::string& Klasse::value() const;
    void Klasse::setValue( const std::string& val );
    

    Mein Problem liegt in der Übergabe der Signatur an das Template:

    template< typename PropUser, typename ValueType >
    class Property
    {
    public:
        Property( PropUser* user, void (PropUser::*setter)(ValueType), ValueType (PropUser::*getter)() const, const ValueType& defaultVal=ValueType() )
        {
            m_user = user;
            set = setter;
            get = getter;
            setValue(defaultVal);
        }
    
    [...]
    private:
        PropUser* m_user;
        void (PropUser::*set)(ValueType);
        ValueType (PropUser::*get)() const;
    

    So schaut es jetzt aus, und es geht damit ja nur mit der Variante für elementare Datentypen.

    boost::mem_fn / boost::function sind wohl Alternativen, aber auch hier muss ich die Signatur mit übergeben.

    Jetzt könnte ich natürlich 2 Property-Klassen implementieren, eine für die Referenz- die andere für die Elementar-Variante.
    Wenn das eine Möglichkeit ist, geht es mit virtuellen Funktionen bei Templates gut? Oder sollte ich templates spezialisieren? Wie könnte zweiteres mit den beiden Signatur-Varianten ausschauen?

    Oder seht ihr eine Lösung mit Präprozessor-Direktiven? Dass ich also die Signatur einem Makro übergeb?

    Für mich ist die vertiefte Verwendung von Templates, insbesondere mit den Funktionspointern, noch recht neu, und haltlos verrennt hat man sich schnell...
    Wenn mich jemand kurz in die richtige Richtung schupsen könnte, das wäre nett 🙂

    Danke schon mal!



  • Na gut, war gar nicht so schwer. boost::function ist const, Referenz, etc "wurscht", hauptsache der Typ stimmt.
    So konnte ich dieses Problem jetzt lösen:

    template < typename User, typename ValueType, typename Setter, typename Getter >
    class Property2
    {
        User* m_user;
        Setter m_setter;
        Getter m_getter;
    public:
        Property2( User* user, Setter set, Getter get )
         : m_user(user), m_setter(set), m_getter(get)
        {}
    [...]
    

    Verwendung dann so:

    class TestProp2
    {
        Property2<TestProp2, int, boost::function<void(TestProp2*, int)>, boost::function<int(TestProp2*) > > prop;
        int m_val;
        public:
            TestProp2()
            : prop(this, &TestProp2::setVal, &TestProp2::val)
            {
            }
    
            void setVal( int val )
            {
                m_val = val;
            }
    
            int val() const
            {
                return m_val;
            }
    
            Property2<TestProp2, int, boost::function<void(TestProp2*, int)>, boost::function<int(TestProp2*) > >& property()
            {
                return prop;
            }
    };
    

    Nur hab ich jetzt ein weiteres Problem. Ich wollte Property-Objekte in eine map<string, Property> packen, damit man von außen die Klassen, die Properties nutzen ganz einfach steuern kann, ohne die Schnittestelle zu kennen:

    Person mein_opa;
    mein_opa.setProperty( "Alter", 97 );
    mein_opa.setProperty( "Geburtstag", Date( 22, 1, 1912 ) );
    

    Das ist aber nicht möglich, da ein Property<Person, int, ...> was anderes als ein Property<Person, Date, ...> ist, und das nicht zusammen in eine Liste gepackt werden kann. (Hätte mir ja eigentlich auch vorher schon klar sein können).

    Wäre eine Option, die ganzen properties einzeln als Member zu speichern. Aber das impliziert, dass der Programmierer bei neuen Klassen auch seine setProperty() und getProperty()-Methode selber implementiert. Und da kann man auf die properties als Datenmember eigentlich auch gleich verzichten, da der die Schnittstelle seiner Klasse direkt verwenden kann.
    Ich wollte aber eine allgemeine Lösung. Wenn mir da noch jemand einen Tip geben kann, wäre nett 🙂

    Ansonten werde ich das mit der Property-Klasse erstmal verwerfen...

    Grüße
    Kurt





  • drakon schrieb:

    Vielleicht hilft dir das hier:
    http://www.codeproject.com/KB/cpp/cppproperties.aspx

    Danke, aber wirklich hilft mir das nicht.

    property<PropTest,int,READ_WRITE> Count;
    

    Das ist ja wieder ein Member, mit fixen template-Typen. Drum kann ich das wieder nicht in ne map mit key==std::string bringen.

    Ich hab das jetzt erstmal über Makros gemacht. Denn im Endeffekt braucht die setProperty-Methode, um flexibel zu sein, auch wieder ein boost::any als value.

    So schauts grad aus:

    #define BEGIN_READ_PROPERTY \
    public: \
    boost::any property(const std::string& key) \
    { \
        if( false ) {} \
    
    #define READ_PROPERTY(Key, Reader) \
        else if( #Key == key ) { \
            return boost::any(Reader()); \
        }\
    
    #define END_READ_PROPERTY \
        else { \
            std::cout << "There is no Key >>" + key + "<<" + ". property() failed" << std::endl; \
            return boost::any(); \
        } \
    } \
    
    #define BEGIN_WRITE_PROPERTY \
    public: \
    bool setProperty(const std::string& key, const boost::any& val) \
    { \
    try { \
        if( false ) {} \
    
    #define WRITE_PROPERTY(Key, Writer, ValueType) \
        else if( #Key == key ) { \
            Writer( boost::any_cast<ValueType>(val) ); \
        }\
    
    #define END_WRITE_PROPERTY \
        else { \
            std::cout << "There is no Key >>" + key + "<<" + ". setProperty() failed" << std::endl; \
        } \
    } catch( boost::bad_any_cast e ) { \
        std::cout << "Bad Any Cast, could not set Property >>" + key + "<<" << std::endl; \
    } \
    } \
    
    ////////////////////////
    
    struct TestStruct
    {
        int val;
        TestStruct(int v=0)
         : val(v)
        {}
        TestStruct( const TestStruct& other )
        {
            std::cout << "Copy" << std::endl;
            this->val = other.val;
        }
        const TestStruct& operator=( const TestStruct& other )
        {
            std::cout << "op =" << std::endl;
            this->val = other.val;
            return *this;
        }
    };
    
    //////////////////////
    
    class MakroTest
    {
        TestStruct m_val;
    public:
        void setValue( const TestStruct& val ) {
            m_val = val;
        }
        const TestStruct& getValue() const {
            return m_val;
        }
    
        BEGIN_READ_PROPERTY
        READ_PROPERTY(Test1, getValue)
        END_READ_PROPERTY
    
        BEGIN_WRITE_PROPERTY
        WRITE_PROPERTY(Test1, setValue, TestStruct)
        END_WRITE_PROPERTY
    };
    

    Sind zwar mit boost::any pro Zugriff zwei Kopierkonstruktoren im Spiel, aber das ist wohl der Preis für die Flexibilität 🙂

    Thx
    Kurtl





  • Ich weiß nicht, vllt. ist ja "Property" der falsche Ausdruck. Ich will nicht mit C++ die Property-Syntax von C# nachbauen. (Kenn ich eh nicht persönlich, nur aus den ganzen Google-Funden) Ich möchte eigentlich nur die Möglichkeit, setter/getter-Methoden einer Klasse über eine einheitliche Schnittstelle aufzurufen. Hier property() und setProperty(). Wobei über die Keys die entsprechende Funktion aufgerufen ist.
    Ich will nicht speziellen Syntactic Sugar ala "opa.alter = 97;".

    Hintergrund ist der, dass ich Plugins lade. Diese Plugins sollen gesteuert werden können. Da die ladende Instanz nicht die Schnittstelle (z.B. opa.setAlter(); ) kennt, muss man einen Umweg gehen. Mit den Plugins wird eine Plugin-Description ausgeliefert. Darin stehen z.B. Sachen wie "PluginName", "PluginVersion" oder auch "PluginDependencies", wenn das Plpugin auf andere Plugins angewiesen ist. Und ebenfalls findet man dort eine Beschreibung der möglichen Properties, ala

    <property key="Alter", type="int">
    

    Mit der Info kann ich nun "ganz einfach" z.B. in ner Gui Elemente zum Steuern erstellen (für int (Alter) z.B. eine "SpinBox"). Wenn die SpinBox ihren Wert ändert, kann das direkt an das "opa"-Objekt gesandt werden:

    opa.setProperty("Alter", spinBox.value());
    

    Und genau das macht jetzt meine Macro-Implementierung (hab sie mittlerweile erweitert, da man ja auch Vererbungshierarchien abdecken will), und ich bin furchtbar glücklich 🙂 Weil der Nutzer nur simple Macros tippen muss, und nicht selber die ganzen if/else schreiben muss.

    Der Versuch mit einer Property-Klasse war denke ich eine Sackgasse.

    Grüßle


Anmelden zum Antworten