20
\$\begingroup\$

I am currently developing a new breed of 3D engine for my upcoming thesis and I really liked the C# properties, but now I am on C++11 for obvious reasons. Since I couldn't find it elsewhere, I tried to write my own property mechanism that shall nicely integrate with C++.

I'd appreciate expert feedback on my class (or at least people who know C++11 much better than I do); after all I am working with C++ for just a few weeks now.

The usage of my PROPERTY<T> class is like this:

struct MyClass{
    PROPERTY<int> myProp;
}

MyClass m = MyClass();
m.myProp *= 3;
int val = m.myProp;

And all the other stuff you can think of...

While it is rather easy to accomplish the obvious (see above) of such a mechanism, it gets a little more complicated if you also want to take into account lambda expressions as well as rare constructs (where all three operator template parameters are of a different type) along the lines of:

PROPERTY<NgMatrix> myMat; 
PROPERTY<NgVector3> myVec;
PROPERTY<NgVector4> res = myMat * myVec;

This obviously requires the *operator already to be implemented on NgMatrix4. The property mechanism just forwards them.

I posted the update below, since there are quite a lot of changes. If no one has an idea how to get rid of using member variables for storing the getter/setter, I will drop this approach. It just has too much memory footprint as it is now. But maybe someone still has use for this class and for me it was also a chance to further play around with C++.

Adding accessors was easier said than done, but here it is:

template<class T, class TGetter = boost::function<T& ()>, class TSetter = boost::function<void (T)>>
struct PROPERTY
{
    template<class X, class Y, class Z> friend struct PROPERTY; // makes this a friend to all instanciations
private:
    TGetter m_Getter;
    TSetter m_Setter;
    T m_ValueIfNoAccessors;

    T& get() 
    { 
        if(!m_Getter.empty()) 
            return m_Getter(); 
        else 
            return m_ValueIfNoAccessors; 
    }

    const T& get() const 
    { 
        if(!m_Getter.empty()) 
            return m_Getter(); 
        else 
            return m_ValueIfNoAccessors; 
    }

    void set(T newValue) 
    { 
        if(!m_Setter.empty()) 
            m_Setter(newValue); 
        else 
            m_ValueIfNoAccessors = newValue; 
    }

    typename typedef PROPERTY<T, TGetter, TSetter> prop_type;

public:
    PROPERTY() : m_Getter(), m_Setter(), m_ValueIfNoAccessors() { set(T()); }
    PROPERTY(T initValue) : m_Getter(), m_Setter(), m_ValueIfNoAccessors() { set(initValue); }
    PROPERTY(TGetter getter) : m_Getter(getter), m_Setter(), m_ValueIfNoAccessors() { set(T()); }
    PROPERTY(TGetter getter, TSetter setter) : m_Getter(getter), m_Setter(setter), m_ValueIfNoAccessors() { set(T()); }

    operator T() { return get(); }

    template<class X, class Y, class Z>
    prop_type& operator=(PROPERTY<X,Y,Z>& other)
    {
        set(other.get());

        return *this;
    }

    template<class TI>
    prop_type& operator=(const TI& other)
    {
        set(other);

        return *this;
    }

    /*
        This operator was introduced for simplifying access to call operators:

        PROPERTY<boost::function<int (int)>> myFunc;

        myFunc(345); // ERROR!
        myFunc()(345); // now is valid...
    */
    T& operator()() { return get(); }
    const T& operator()() const { return get(); }

    /*
        Beaware of the fact, that anyone really wanting to change the backing value
        of a property sure can, by simply doing:

        PROPERTY<T> myProp;
        T* myRef = myProp.operator->();

        So do not rely on Getter/Setter safety in case of any security critical scenario.
    */
    T* operator->() { return &get(); }
    const T* operator->() const { return &get(); }

    template<class TI> prop_type& operator/=(TI newValue) { set(get() / newValue); return *this; }
    template<class TI> prop_type& operator+=(TI newValue) { set(get() + newValue); return *this; }
    template<class TI> prop_type& operator-=(TI newValue) { set(get() - newValue); return *this; }
    template<class TI> prop_type& operator*=(TI newValue) { set(get() * newValue); return *this; }

    template<class TI> auto operator/(TI newValue) const -> decltype(T() / TI()) { return get() / newValue; }
    template<class TI> auto operator+(TI newValue) const -> decltype(T() + TI()) { return get() + newValue; }
    template<class TI> auto operator-(TI newValue) const -> decltype(T() - TI()) { return get() - newValue; }
    template<class TI> auto operator*(TI newValue) const -> decltype(T() * TI()) { return get() * newValue; }


};

Please note that copy-constructor as well as other semantics like that are currently not reflected correctly! You need to add them, but the look very similar to the existing assignment operator.

Now you can use properties the "usual" way in a class, at least if you don't take initialization into account:

struct CTestClass
{
private:
    float m_AspectRatio;
    int m_Left;
    int m_Right;

public:
    PROPERTY<float> AspectRatio;
    PROPERTY<int> Left;
    PROPERTY<int> Right;

    CTestClass()
    {
        AspectRatio = PROPERTY<float>(
            [&]() -> float& { return m_AspectRatio; }, 
            [&](float newValue) { m_AspectRatio = newValue; });

        Left = PROPERTY<int>(
            [&]() -> int& { return m_Left; }, 
            [&](int newValue) { m_Left = newValue; });

        Right = PROPERTY<int>(
            [&]() -> int& { return m_Right; }, 
            [&](int newValue) { m_Right = newValue; });
    }
};

Accessing these properties look like this and seems "normal":

CTestClass myClass = CTestClass();

myClass.AspectRatio = 1.4;
myClass.Left = 20;
myClass.Right = 80;
myClass.AspectRatio = myClass.AspectRatio * (myClass.Right - myClass.Left);

EDIT:

It is also possible to overload properties this way, even though it gets a little more cumbersome. You would just need the original setter/getter implemented as class member functions (virtual of course). And the just call them from inside the lambda expressions. Now if you want to override the property of a super class, just override the corresponding setter/getter member function and the original property will be redirected to your new implementation.

\$\endgroup\$

migrated from stackoverflow.com Jan 13 '12 at 21:15

This question came from our site for professional and enthusiast programmers.

  • 3
    \$\begingroup\$ How is this different than having a public field? What's the point? \$\endgroup\$ – ildjarn Jan 13 '12 at 20:53
  • 3
    \$\begingroup\$ Ok, so how is it different than having public getter/setter member functions, which is what this will end up having? Why bother with the extra layer of indirection? What is it getting you? \$\endgroup\$ – ildjarn Jan 13 '12 at 20:59
  • 4
    \$\begingroup\$ There is not really another layer of indirection, since it is hidden and will be optimized away by modern compilers anyway. Many people, including me, find the use of getter/setter member functions quite disgusting and this is why there are languages, as C#, which provide properties. So the "need" of properties is not really something to discuss within this thread ;). \$\endgroup\$ – thesaint Jan 13 '12 at 21:02
  • 3
    \$\begingroup\$ The biggest favour you can do yourself, which you'll appreciate perhaps only after a few years, is to forget everything you learnt when doing C# and embrace C++ as a new and very different language. \$\endgroup\$ – Kerrek SB Jan 13 '12 at 21:12
  • 3
    \$\begingroup\$ Everywhere I go looking into accomplishing this very thing, existing C++ developers are saying that this approach is useless or wrong. Well that's not the case at all. As a person who knows languages that provide this feature as well as C++, this is a nice feature to have and doesn't break any rules. The most common objection I see is that it somehow breaks encapsulation. That's nonsense, since as someone accurately pointed out here it's essentially the same as just having a public get/set for a protected member. It's simply a different style, syntactic sugar and nothing more. \$\endgroup\$ – user7535 Feb 20 '12 at 1:38
5
\$\begingroup\$

The idea of encapsulating your properties with a template is not bad, but, you are are missing the power of properties' accessors. There is not much difference on using properties without been able to override them.

Accessors allow a property to "communicate" with its containning class, and to add extra functuonality if the are declared "virtual".

In the following example, I try to implement "property" with inheritance & method pointers, instead of templates. I consider the template techique much better.

Please ignore the direct implementation of properties, and check, how the property, interacts with the container class and its members, and viceversa, in the accessors methods. Take a look to the class code related to the accesors.

(The example may not run, I didin't have the real code at hand, so I coded from memory)

// ------------
// "properties.hpp"
// ------------

// dummy class to contain properties
class PropertyBaseClass
{
public:
    PropertyBaseClass() ; 
    ~PropertyBaseClass() ;
} ;

class PROPERTY
{
protected:
    PropertyBaseClass* Container;
public:
    PROPERTY(PropertyBaseClass* AContainer) { Container = AContainer; }
    ~PROPERTY() { Container = NULL ; }
} ;

// real class with properties
class PropertyClass
{
protected:
  List<PROPERTY> Properties;
public:
    PropertyBaseClass() { Properties = List<PROPERTY>(); }
    ~PropertyBaseClass() { Properties = NULL; }
} ;

typedef
  int (PROPERTY::*IntegerGetter)();

typedef
  (PROPERTY::*IntegerSetter)(int AValue);

class IntegerPROPERTY
{
protected:
    PropertyBaseClass* Container;

    int Data;

    IntegerGetter getter();
    IntegerSetter setter();
public:
    PROPERTY(PropertyBaseClass* AContainer) ;
    ~PROPERTY() ;

    int getValue() ;
    void setValue(int AValue) ;
} ;

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

// ...


// ------------
// "properties.cpp"
// ------------

class PROPERTY
{
protected:
    PropertyBaseClass* Container;
public:
    PROPERTY(PropertyBaseClass* AContainer) { Container = AContainer; }
    ~PROPERTY() { Container = NULL ; }
} ;

IntegerPROPERTY::IntegerPROPERTY(PropertyBaseClass* AContainer, IntegerGetter AGetter, IntegerSetter ASetter)
{
  this.Container = AContainer;
  this.getter = AGetter;
  this.setter = ASetter;
}

IntegerPROPERTY::~IntegerPROPERTY()
{
  this.Container = NULL;
}

int IntegerPROPERTY::getValue()
{
  int Result = 0;

  if (this.getter != NULL)
  {
    Result = this.getter();
  }

  return Result;
}

void IntegerPROPERTY::setValue(int AValue)
{
  if (this.setter != NULL)
  {
    this.setter(AValue);
  }
}

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

// ...


// ------------
// "Example1.cpp"
// ------------

// inherits from "PropertyClass",
// not from "PropertyBaseClass"
class Area: PropertyClass
{
public:
    IntegerPROPERTY* X1;
    IntegerPROPERTY* Y1;
    IntegerPROPERTY* X2;
    IntegerPROPERTY* Y2;
protected:
    int PX1;
    int PY1;
    int PX2;
    int PY2;

    virtual int getX1();  
    virtual void setX1(int AValue);

    virtual int getY1();  
    virtual void setX1(int AValue);

    virtual int getX2();  
    virtual void setX2(int AValue);

    virtual int getY2();  
    virtual void setY2(int AValue);

    int Width();
    int Height();
public:
    Area() ;

    virtual Prepare();
} ;

void Area::Prepare()
{
  X1 = new IntegerPROPERTY(&this, &Area::getX1, &Area::setX1);
  Y1 = new IntegerPROPERTY(&this, &Area::getY1, &Area::setY1);
  X2 = new IntegerPROPERTY(&this, &Area::getX2, &Area::setX2);
  Y2 = new IntegerPROPERTY(&this, &Area::getY2, &Area::setY2);
}

int Area::getX1()
{
  int Result = PX1; 
  return Result;
}

void Area::setX1(int AValue)
{
  PX1 = AValue;
}

int Area::getX2()
{
  int Result = PX2; 
  return Result;
}

void Area::setX2(int AValue)
{
  PX2 = AValue;
}

void Area::setY1(int AValue)
{
  PY1 = AValue;
}

int Area::getY2()
{
  int Result = PY2; 
  return Result;
}

void Area::setY2(int AValue)
{
  PY2 = AValue;
}

int Area::Width()
{
  //int Result = (PX2 - PX1); 

  int Result = (this->X2->getter() - X1->getter()); 
  return Result;
}

int Area::Height()
{
  //int Result = (PY2 - PY1);

  int Result = (this->Y2->getter() - Y1->getter());  
  return Result;
}

int main(...)
{
  Area MyArea = new Area();
  MyArea->Prepare();

  MyArea->X1->setter(3);
  MyArea->X2->setter(5);
  MyArea->Y1->setter(23);
  MyArea->Y2->setter(25);

  cout << "X1: " << MyArea->X1->getter(); 
  cout << "Y1: " << MyArea->Y1->getter(); 
  cout << "X2: " << MyArea->X2->getter(); 
  cout << "Y2: " << MyArea->Y2->getter(); 
  cout << "X2: " << MyArea->Width(); 
  cout << "Y2: " << MyArea->Height(); 

  return 0;
}

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

Take a look to the Width and Height functions. They depend on the value of the properties, but, are part of the class.

I add a second example, its the same previous code, plus using a descendant container class, that validates that the first coordinate is always lesser than the second coordinate, by overriding the accesors.

// ------------
// "Example2.cpp"
// ------------

// inherits from "PropertyClass",
// not from "PropertyBaseClass"
class Area: PropertyClass
{
public:
    IntegerPROPERTY* X1;
    IntegerPROPERTY* Y1;
    IntegerPROPERTY* X2;
    IntegerPROPERTY* Y2;
protected:
    int PX1;
    int PY1;
    int PX2;
    int PY2;

    virtual int getX1();  
    virtual void setX1(int AValue);

    virtual int getY1();  
    virtual void setX1(int AValue);

    virtual int getX2();  
    virtual void setX2(int AValue);

    virtual int getY2();  
    virtual void setY2(int AValue);

    int Width();
    int Height();
public:
    Area() ;

    virtual Prepare();
} ;

class ValidArea: Area
{
protected:
    virtual void setX1(int AValue);
    virtual void setX1(int AValue);
    virtual void setX2(int AValue);
    virtual void setY2(int AValue);
public:
    Area() ;

    virtual Prepare();
} ;

void Area::Prepare()
{
  X1 = new IntegerPROPERTY(&this, &Area::getX1, &ValidArea::setX1);
  Y1 = new IntegerPROPERTY(&this, &Area::getY1, &ValidArea::setY1);
  X2 = new IntegerPROPERTY(&this, &Area::getX2, &ValidArea::setX2);
  Y2 = new IntegerPROPERTY(&this, &Area::getY2, &ValidArea::setY2);
}

void ValidArea::setX1(int AValue)
{
  if (AValue > PX2)
  {
    PX1 = PX2;  
    PX2 = AValue;  
  }
  else
  {
    PX1 = AValue;  
  }
}

void ValidArea::setX2(int AValue)
{
  if (AValue < PX1)
  {
    PX1 = PX2;  
    PX2 = AValue;  
  }
  else
  {
    PX1 = AValue;  
  }
}

void ValidArea::setY1(int AValue)
{
  if (AValue > PY2)
  {
    PY1 = PY2;  
    PY2 = AValue;  
  }
  else
  {
    PY1 = AValue;  
  }
}

void ValidArea::setY2(int AValue)
{
  if (AValue < PX1)
  {
    PX1 = PX2;  
    PX2 = AValue;  
  }
  else
  {
    PX1 = AValue;  
  }
}

int main(...)
{
  Area MyArea = new Area();
  MyArea->Prepare();

  MyArea->X1->setter(3);
  MyArea->X2->setter(5);
  MyArea->Y1->setter(23);
  MyArea->Y2->setter(25);

  cout << "X1: " << MyArea->X1->getter(); 
  cout << "Y1: " << MyArea->Y1->getter(); 
  cout << "X2: " << MyArea->X2->getter(); 
  cout << "Y2: " << MyArea->Y2->getter(); 
  cout << "X2: " << MyArea->Width(); 
  cout << "Y2: " << MyArea->Height(); 

  return 0;
}

In these examples, the properties, wheter templates or inheritance, have fields that represent each property accesors. The real accessors are in the class, as it happens with other O.O. languages like C#, Object Pascal, VB.Net.

I usually use accessors that are protected or public, never private, and always virtual.

I agree that accessors are complex, and add some "overheard" to the programs, but, in most of the scenarios, are very be useful.

\$\endgroup\$
  • \$\begingroup\$ Thanks for your code but I think this is not what I want. I know there are plenty of approaches like yours around in the web but I really wanted something that feels like C# properties, otherwise I'd really rather just use member functions as getter/setter... But thanks for reminding me that C# properties can be virtual. That is indeed another flaw of my implementation above... Hmm. Damn. Oh well seems to be easy to fix, just put references to virtual member functions into the lambda expressions... \$\endgroup\$ – thesaint Jan 13 '12 at 22:32
  • \$\begingroup\$ @thesaint The idea wasn't that you use my code, but, that you consider using accessors. The idea of my code was to show an example or why accessors are useful. ;-) \$\endgroup\$ – umlcat Jan 13 '12 at 22:38
  • \$\begingroup\$ Ok, what do you mean by accessors? My code above also has the getter/setter, so maybe I am just overlooking something in your code?! \$\endgroup\$ – thesaint Jan 13 '12 at 22:40
  • \$\begingroup\$ @thesaint Cool. I overlook you made an update, with the accesor methods implemented with closures. You may want to try make a test where your class has a function that use your properties like the Width or Height example, and will be ready ;-) \$\endgroup\$ – umlcat Jan 14 '12 at 15:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.