6

I'm trying to set up a syntactical sugar similar to the c# property concept.

I've read this post: C#-like properties in native c++?. It's helpful, but lacks the design I want. I also respectfully disagree with several of the assertions made there that the property concept is bad oo form, as I fail to see the difference between a set of methods titled get_id() and set_id() and an operator overload that exposes the same concept, allows code to be cleaner when the class is consumed, and generally discourages the public access to private fields, also decoupling the public implementation from private design.

However, the code that I've come up with (inspired by that link) is REALLY kludgy and will be quite difficult to maintain. I'm wondering if anyone has any suggestions to clean this up, or more importantly, knows a better way to do this.

class someClass
{
public:
    struct someProperty_struct{
        virtual someProperty_struct& operator=(const int&){return *this;}
        virtual operator int(){return 0;}
    }someProperty;
};

class derivedClass : someClass
{
    int i;
public:
    struct intProperty: someClass::someProperty_struct
    {
    protected:
        friend class derivedClass;
        derivedClass *outer;
    public:
        virtual someProperty_struct& operator=(const int& value){
            outer->i = value;
            return *this;}
        virtual operator int(){return outer->i;}
    };
    derivedClass()
    {
        intProperty p = intProperty();
        p.outer = this;
        someProperty = p;
    }
};

class consumerClass
{
public:
    someClass* data;
    consumerClass()
    {
        data = (someClass*)(new derivedClass());
    }
    void doSomething()
    {
        data->someProperty = 7;
        int x = data->someProperty;
    }
};

EDIT 1: It occurs to me that I didn't reveal anything about my intentions with this. It's going to be used in a scheduling program, and we're going to be doing a lot of comparison and assignment of all the data in the classes. 'someClass' will effectively be an interface on the data (very few methods needed, lots of data that should be relatively transparent). It will be defined in a static library that the executable will link. 'derivedClass' will effectively be an external implementation, implemented in a dll which will be dynamically loaded. The reason for this is to enable "hot swapping" of the dll with another one which implements a different file backend. We have plans to implement xml, sqlite, and mysql storage backends using a plugin system to load them up.

So basically, I need a design that allows someClass to be a virtual interface that is inherited by derivedClass, which is loaded by factory method, passed through the plugin system, and used in the end by consumerClass.

Community
  • 1
  • 1
lassombra
  • 417
  • 3
  • 15
  • So, after a lot of reflexion, I found the problem: you didn't say what the proposed solution lacks: in your case it lacks a way to specify a custom getter and a custom setter. – J.N. Feb 19 '12 at 00:54
  • Are you referring to my proposed solution, or the one in the referenced link? The point of this is that someClass will be effectively pure virtual, which will then be derived and implemented by some other library, hence the need for pure virtual and inheritance. The getter and setter provided in the intProperty in my implementation is custom, and can be manipulated as needed to provide all sorts of useful tools (in my case, I plan on using it for lazy loading from an xml file) – lassombra Feb 19 '12 at 01:01
  • 1
    OK, then I got it wrong. I was wondering why you didn't use the simple templated solution proposed in the question you referenced. What you need is a property as part of an interface. That's not what I was trying to come up with. I'll see if I have an idea. – J.N. Feb 19 '12 at 01:50
  • Yeah, I think that template is great for matching c#'s "automatic properties" if you want to have a field like syntax that you can later convert (the whole point of c#'s automatic property is that it starts as effectively just a field, but you can modify it later without changing consumer code). I should be a bit more specific about what I'm doing here, this is for a scheduling program. I'll put a bit more details about the intended usage in the original post. – lassombra Feb 19 '12 at 15:39
  • Does it need to be very high performance ? Because another way to see the problem would be to implement your properties in a dictionary-like structure. Now that may not fit the way you want to use it. Then, another idea, would be to wrap the code with macros to make the declaring of the properties easier (a lot could be factored in macros, but macros should not be abused, especially if you could accept something like `object.property() = value;`). – J.N. Feb 20 '12 at 00:32
  • Performance is somewhat an issue as it will be sorting through over a hundred people each of which have a nutso number of rules, and matching each of these people to a slot on the schedule that they match. Since these properties are wrapping data that will be used for the matching, it's kind of important that they be relatively quick, as the data will be accessed several times during the process, and I'd really rather not try and cache the results. I have considering ditching this concept and going for an eager loading semantic, or a manual loading one. – lassombra Feb 20 '12 at 16:13
  • I've never really gotten any skill with preprocessor macros. I have no problem with the design there (I assume that uses the &<> operator() overload?) but I have no idea how I would implement macros to deal with this. Especially in the inheritance part of the scheme. – lassombra Feb 20 '12 at 16:15

2 Answers2

2

So basically, I need a design that allows someClass to be a virtual interface

If I understand you correctly, this intention contradicts the solution you have came up with. The virtual inheritance concerns only virtual member functions. And there is no way you could dynamically inherit nested structs because it is a subject of the lexical meta-programming which is not supported in C++.

My suggestion is to think carefully if you can make your object immutable. Then some adaptation of the Builder/Factory design pattern will allow you to eliminate the usage of properties.

You may also consider to write a code generator. In case if you pick up a good markup keywords, it may be easy.

UPDATE I've added some code to clarify my suggestion.

First of all we prepare some auxiliary objects. They should be put in a header file accessible for both the Library and the Client. These objects will never be modified.

GetSetProp <>----> IGetSetProp <----- PropFunctionAdapter

template<class _Ty>
struct __declspec(novtable) IGetSetProp
{
    typedef std::tr1::shared_ptr<IGetSetProp<_Ty>> ptr_t;
    virtual void set_Prop(_Ty const& val);
    virtual _Ty get_Prop() const;
};

template<typename _Ty>
class PropFunctionAdapter : public IGetSetProp<_Ty>
{
    std::function<_Ty(void)> getter;
    std::function<void(_Ty const&)> setter;
public:
    PropFunctionAdapter(std::function<_Ty(void)> _getter, std::function<void(_Ty const&)> _setter)
        : getter(_getter)
        , setter(_setter)
    {
         // One may want to verify that getter and setter are not empty
    }

    virtual ~PropFunctionAdapter() throw() {}

    inline static std::tr1::shared_ptr<typename PropFunctionAdapter<_Ty>> Create(std::function<_Ty(void)> _getter, std::function<void(_Ty const&)> _setter)
    {
        return std::make_shared<typename PropFunctionAdapter<_Ty>>(_getter, _setter);
    }

public:
    void set_Prop(_Ty const& val)
    {
        setter(val);
    }

    _Ty get_Prop() const
    {
        return getter();
    }
};

template<class _Owner, class _Ty>
typename IGetSetProp<_Ty>::ptr_t CreateAdapter(_Owner& source, _Ty(_Owner::*getter)() const, void(_Owner::*setter)(_Ty const&))
{
    return PropFunctionAdapter<_Ty>::Create(
        std::tr1::bind(std::mem_fn(getter), &source),
        std::tr1::bind(std::mem_fn(setter), &source, std::tr1::placeholders::_1));
}

template<class _Ty>
class GetSetProp
{
    typename IGetSetProp<_Ty>::ptr_t prop;
public:
    GetSetProp(typename IGetSetProp<_Ty>::ptr_t _prop)
        : prop(_prop)
    {
    }

    GetSetProp<_Ty>& operator= (_Ty const& val)
    {
        prop->set_Prop(val);
        return *this;
    }

    operator _Ty() const
    {
        return prop->get_Prop();
    }
};

Similarly you may define GetProperty and SetProperty.

Suppose you have a data contract containing two fields Pressure:int and Description:string. Then you define the data contract:

class BaseClass
{
public:
    GetSetProp<int> Pressure;
    GetSetProp<std::string> Description;
protected:
    BaseClass(IGetSetProp<int>::ptr_t fieldA, IGetSetProp<std::string>::ptr_t fieldB)
        : Pressure(fieldA)
        , Description(fieldB)
    {
    }

    virtual ~BaseClass() throw() {}
};

And its implementation in the library:

class DerivedClass : public BaseClass
{
public:
    // Here you initialize fields assigning them correspondent setters and getters
    // You may define an ATL-like mapping in order to hide implementation details
    DerivedClass()
        : BaseClass(
            CreateAdapter(*this, &DerivedClass::get_Pressure, &DerivedClass::set_Pressure)
            , CreateAdapter(*this, &DerivedClass::get_Description, &DerivedClass::set_Description))
    {
    }

    virtual ~DerivedClass() throw() {}

private:
    void set_Pressure(int const& value)
    {
        val = value;
    }

    int get_Pressure() const
    {
        return val;
    }

    void set_Description(std::string const& description)
    {
        this->description = description;
    }

    std::string get_Description() const
    {
        return description;
    }

private:
    int val;
    std::string description;
};

// The client-side code
DerivedClass d;
BaseClass& b = d; 
b.Description = "Hello";
std::string descr = b.Description;
b.Pressure = 2;
int value = b.Pressure;
Igor Chornous
  • 1,068
  • 1
  • 6
  • 10
  • The problem with this is that you have totally denied the stated intent of the design. The intent is for someClass to effectively be a data contract, complete with property like constructs. derivedClass would be the *hidden* implementation of that contract, implemented through a factory in a dynamically loaded context – lassombra Feb 22 '12 at 22:30
  • I had to reread that code definition 3 times before I really picked up on what was going on. I'm a little hesitant to trust that because of your removing the vtable in one part, and that the design leaves a lot of implementation details in the base class (allowing the derived class to only provide the actual accessors). However, it does look like it accomplishes almost exactly what I want with operator overloading gaining access to internal methods and looking nice in design. – lassombra Feb 24 '12 at 17:30
  • 1
    I've removed the vtable in order to make IGetSetProp act as an interface. It is a well known trick in the C++ world – Igor Chornous Feb 24 '12 at 18:23
  • my understanding of the vtable is that it's REQUIRED to make inheritance work when the consumer class has no knowledge of the derived class. – lassombra Feb 25 '12 at 15:03
  • 2
    Nevermind, I read up on that declaration. It reads like it would remove the vtable, but instead it just removes the code from the pure abstract class that controls the vtable resulting in faster execution and smaller executables. Makes sense now. Learn something every day. – lassombra Feb 25 '12 at 15:15
  • 1
    The compiler will not generate a code which initializes the vtable. Thus having no input references, the linker may remove it (vtable). There is a good explanation of this modifier [here](http://msdn.microsoft.com/en-us/library/k13k85ky(v=vs.71).aspx). But look IGetSetProp doesn't need a vtable at all since it will never be instantiated. This class serves only to oblige its children to have certain methods implemented. – Igor Chornous Feb 26 '12 at 00:13
  • Alright, after some debate, and some playing around, I've decided to use this example. It really is well done, if I could vote it up again, I would. Anyways, I expanded the example to include one-sided properties (just get or just set) as well as index properties (someclass.SomeProp[ix]). That was the most fun as it allowed me to really play with it and prove to myself that I understood the sample provided. I have to admit to liking the syntactical sugar it provides. It also doesn't particularly bloat my exe, I suspect the compiler optimizes it down quite a bit. – lassombra Feb 27 '12 at 04:48
1

Creating a property helper class that must be instanciated as a member of the class you want to add property on is a bad idea, since you'll have the cost of the property members for all the instances of this class.

A solution that offers a "property" mechanism close to what other languages offer can be made available in C++ using something like what I describe later.

The only limitation being that you access the property using

instance.property(); // to get

and

instance.property() = 42; // to set

#include <iostream>
#include <string>

using namespace std;

// ------------------------------------------------------------------

#define PROPERTY_GET_SET(CLASS, NAME, TYPE) GetSetProperty<CLASS, TYPE> NAME() { return GetSetProperty<CLASS, TYPE>(this, &CLASS::get_##NAME, &CLASS::set_##NAME); }
#define PROPERTY_GET(CLASS, NAME, TYPE)     GetProperty<CLASS, TYPE> NAME()    { return GetProperty<CLASS, TYPE>(this, &CLASS::get_##NAME); }
#define PROPERTY_SET(CLASS, NAME, TYPE)     SetProperty<CLASS, TYPE> NAME()    { return SetProperty<CLASS, TYPE>(this, &CLASS::set_##NAME); }

template <typename CLASS, typename TYPE>
struct GetSetProperty {
    typedef TYPE (CLASS::*Getter_t)() const;
    typedef void (CLASS::*Setter_t)(TYPE);
    GetSetProperty(CLASS* instance, Getter_t getter, Setter_t setter) : m_instance(instance), m_getter(getter), m_setter(setter) {}
    operator TYPE() const { return (this->m_instance->*this->m_getter)(); }
    GetSetProperty<CLASS, TYPE>& operator=(TYPE value) { (this->m_instance->*this->m_setter)(value); return *this; }
    CLASS* const   m_instance;
    const Getter_t m_getter;
    const Setter_t m_setter;
};

template <typename CLASS, typename TYPE>
struct GetProperty {
    typedef TYPE (CLASS::*Getter_t)() const;
    GetProperty(CLASS* instance, Getter_t getter) : m_instance(instance), m_getter(getter) {}
    operator TYPE() const { return (this->m_instance->*this->m_getter)(); }
    CLASS* const   m_instance;
    const Getter_t m_getter;
};

template <typename CLASS, typename TYPE>
struct SetProperty {
    typedef void (CLASS::*Setter_t)(TYPE);
    SetProperty(CLASS* instance, Setter_t setter) : m_instance(instance), m_setter(setter) {}
    SetProperty<CLASS, TYPE>& operator=(TYPE value) { (this->m_instance->*this->m_setter)(value); return *this; }
    CLASS* const   m_instance;
    const Setter_t m_setter;
};

template <typename CLASS, typename TYPE>
ostream& operator<<(ostream& ostr, const GetSetProperty<CLASS, TYPE>& p) { ostr << (p.m_instance->*p.m_getter)(); return ostr; }

template <typename CLASS, typename TYPE>
ostream& operator<<(ostream& ostr, const GetProperty<CLASS, TYPE>& p) { ostr << (p.m_instance->*p.m_getter)(); return ostr; }

// ------------------------------------------------------------------

class Dummy
{
public:

    Dummy() : m_value1(42) {}

    PROPERTY_GET_SET(Dummy, Value1, int);
    PROPERTY_GET_SET(Dummy, Value2, const string&);

protected:

    virtual int           get_Value1() const { return this->m_value1; }
    virtual void          set_Value1(int value) { this->m_value1 = value; }

    virtual const string& get_Value2() const { return this->m_value2; }
    virtual void          set_Value2(const string& value) { this->m_value2 = value; }

private:

    int    m_value1;
    string m_value2;
};


int main(int argc, char* argv[]) {

    Dummy d;

    cout << d.Value1() << endl;
    d.Value1() = 3;
    cout << d.Value1() << endl;

    cout << d.Value2() << endl;
    d.Value2() = "test";
    cout << d.Value2() << endl;

    return 0;
}

// ------------------------------------------------------------------
rlods
  • 465
  • 2
  • 6
  • 1
    This is a nice, valid answer. I especially like the nice macro examples. Since Macro programming is the one thing I struggle with most, this was helpful. It *may* or may not be the solution I go with, but I'm flagging it as an answer for future readers to find a nice solid answer. – lassombra Feb 25 '12 at 15:23