Click here to Skip to main content
15,895,538 members
Articles / Programming Languages / C#
Tip/Trick

C++11 Properties

Rate me:
Please Sign up or sign in to vote.
4.78/5 (4 votes)
20 Aug 2013CPOL2 min read 25.2K   10   8
C++11: C# like properties

Introduction

Me and my friends recently started to ponder whether we can implement C# like properties in C++11 to be able to transparently add some interesting behaviors to fields of our classes (such as logging accesses, or computing values on the go) while still having this nice C# syntax sugar. Basically we wanted to have field in class that can be declared this way:

C++
class Foo 
{ 
private:
    int _field;
public:
    Property< int > field { 
                               [&]( ){ return _field; },
                               [&]( int value ){ _field = value; } 
                          };
};

Which could be later on used like this:

C++
Foo a;
a.field = 10;
int b = a.field;  

While having good inlining properties (ie. simple properties should be possible to be inlined)

As our implementation attempts showed the biggest problem was specification of functors able to hold getter and setter methods of the property class. So we either had generic classes that could hold lambdas, functors and function pointers - but came with the runtime cost of having virtual function calls, or specialized fields that came with uglier declaration.

Finally we didn't manage to get both nice declaration and good inlining properties at one time, but we came pretty close to that. Of course these examples are not 100% complete as you probably should overload arithmetic operators and such but nonetheless it is a fine working base for that, which moreover lets you debug code far faster (being able to breakpoint variable reads without winDbg was priceless to me) 

The code

Here are our three approaches (tested on GCC 4.8):

First of all we chose a simple implementation using std::function to hold getters and setters. This allowed us to have a nice brace initializer accepting lambdas, but sadly quite a big runtime overhead with 2 virtual function calls per invoke of getter/setter

First with simplest implementation, nice declaration and biggest runtime overhead:

C++
#include <functional>
 
template < typename PropertyType >
struct Property
{
protected:
    std::function< PropertyType( ) > _mGet;
    std::function< void( PropertyType ) > _mSet;
     
public:
    Property( ) { }
     
    Property( const PropertyType & value )
    {
        _mSet( value );   
    }
     
    Property( const Property & lhs ) : _mGet( lhs._mGet ), _mSet( lhs._mSet ) { }
     
    Property( const std::function< PropertyType( ) > & get,
              const std::function< void( PropertyType ) > & set )
        : _mGet( get ), _mSet( set ) { }
     
    Property & operator=( const PropertyType & value )
    {
        _mSet( value );
        return *this;
    }
     
    operator PropertyType( )
    {
        return _mGet( );
    }
};

Afterwards we decided to create our own wrapper for functors (since they had simple signatures) we were able to reduce number of virtual function calls per invoke to one. But still it doesn't give us the performance we would ideally have.

The second approach uses more streamlined functor wrapper that uses only one virtual function call per invoke and shares all the syntax sugar of the previous approach:

C++
#include <functional>
 
template < typename PropertyType_ >
struct PropertyWrapperBase
{
public:
    typedef PropertyType_ PropertyType;
 
protected:
    virtual PropertyType InvokeGetter( ) = 0;
    virtual void InvokeSetter( const PropertyType& value ) = 0;
    virtual void InvokeSetter( PropertyType&& value ) = 0;
 
 
public:
    PropertyType Get( )
    {
        return InvokeGetter( );
    }
 
    void Set( const PropertyType& value )
    {
        InvokeSetter( value );
    }
 
    void Set( PropertyType&& value )
    {
        InvokeSetter( std::forward< PropertyType >( value ) );
    }
 
    virtual ~PropertyWrapperBase( );
};
 
template < typename PropertyType_, typename GetterType_, typename SetterType_ >
struct PropertyWrapper : PropertyWrapperBase< PropertyType_ >
{
private:
    typedef PropertyWrapper MyType_;
    typedef PropertyWrapperBase< PropertyType_ > MyBase_;
public:
    typedef typename MyBase_::PropertyType PropertyType;
 
protected:
    PropertyType InvokeGetter( ) override
    {
        return _getter( );
    }
 
    void InvokeSetter( const PropertyType& value ) override
    {
      _setter( value );
    }
 
    void InvokeSetter( PropertyType&& value ) override
    {
        _setter( std::forward< PropertyType >( value ) );
    }
 
public:
    PropertyWrapper( GetterType_ getter, SetterType_ setter )
        : _getter( getter ), _setter( setter ) { }
 
private:
    GetterType_ _getter;
    SetterType_ _setter;
};
 
 
template < typename PropertyType_ >
struct Property
{
public:
    typedef PropertyType_ PropertyType;
 
    template < typename GetterType_, typename SetterType_ >
    Property(GetterType_ getter, SetterType_ setter)
        : _wrapper(new PropertyWrapper< PropertyType_, GetterType_, SetterType_ >( getter, setter ) ) { }
 
    const PropertyType& operator =( const PropertyType& value )
    {
        _wrapper->Set( value );
        return value;
    }
 
    PropertyType&& operator =( PropertyType&& value )
    {
        _wrapper->Set( std::forward< PropertyType >( value ) );
        return std::move( _wrapper->Get( ) );
    }
 
    operator PropertyType( )
    {
        return _wrapper->Get( );
    }
 
    ~Property( )
    {
        delete _wrapper;
    }
 
private:
    PropertyWrapperBase< PropertyType >* _wrapper;
};

So after spending some time we came to the solution below, which is really good performance wise (simple properties are just as fast as direct field access) but has a bit ugly definition (the need to pass the owner class name to the property) and lack of the nice C# like syntax sugar, but still this should be probably your choice in most cases.

And the last one which has no runtime overhead - but not so nice declaration.

C++
#include <functional>
 
template <
        typename OwnerType_,
        typename ValueType_,
        ValueType_ (OwnerType_::*getter_)( ),
        void (OwnerType_::*setter_)( ValueType_ )
     >
struct Property
{
    Property( OwnerType_* owner )
        : _owner( owner ) { }
     
    const ValueType_& operator=( const ValueType_& value )
    {
        ( _owner->*setter_ )( value );
        return value;
    }
     
    ValueType_&& operator=( ValueType_&& value )
    {
        ( _owner->*setter_ )( std::forward< ValueType_ >( value ) );
        return std::move( ( _owner->*getter_ )( ) );
    }
     
    operator ValueType_( )
    {
        r->*getter_)( );
    }
     
    Property( Property&& ) = default;
    Property( const Property& ) = delete;
    Property& operator=( const Property& ) = delete;
    Property& operator=( Property&& ) = delete;
     
private:
    OwnerType_* const _owner;
};
 
#define CREATE_PROPERTY(className, accessSpec, valueType, propName, getBlock, setBlock) \
    private: valueType _auto_g_get##propName() getBlock \
    private: void _auto_g_set##propName(valueType value) setBlock \
    public: typedef Property<className, valueType, \
    &className::_auto_g_get##propName, \
    &className::_auto_g_set##propName> _auto_g_propType##propName; \
    accessSpec: _auto_g_propType##propName propName = _auto_g_propType##propName(this)

Which is used like that: 

C++
struct A
{
private:
    int _test;
public:
    CREATE_PROPERTY( A, public, int, test,
        { return _test; },
        { _test = 3 * value; } 
    );
};

Points of Interest

The biggest problem was that we weren't able to declare a storage mechanism for functors or methods that either had no virtual function call overhead or didn't require explicit passing of the owning class name.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Poland Poland
Graphics drivers developer, programming mostly in C/C++

Comments and Discussions

 
GeneralMy vote of 5 Pin
R3AL26-Aug-13 4:59
R3AL26-Aug-13 4:59 
GeneralRe: My vote of 5 Pin
Stanski Adam26-Aug-13 20:45
Stanski Adam26-Aug-13 20:45 
QuestionSimpler solution Pin
steveb21-Aug-13 2:14
mvesteveb21-Aug-13 2:14 
AnswerRe: Simpler solution Pin
Stanski Adam21-Aug-13 3:37
Stanski Adam21-Aug-13 3:37 
GeneralRe: Simpler solution Pin
steveb21-Aug-13 10:11
mvesteveb21-Aug-13 10:11 
GeneralRe: Simpler solution Pin
Stanski Adam21-Aug-13 20:55
Stanski Adam21-Aug-13 20:55 
GeneralMy vote of 5 Pin
Amir Mohammad Nasrollahi20-Aug-13 6:19
professionalAmir Mohammad Nasrollahi20-Aug-13 6:19 
GeneralMy vote of 5 Pin
Florian Rosmann20-Aug-13 5:40
Florian Rosmann20-Aug-13 5:40 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.