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:
class Foo
{
private:
int _field;
public:
Property< int > field {
[&]( ){ return _field; },
[&]( int value ){ _field = value; }
};
};
Which could be later on used like this:
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:
#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:
#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.
#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:
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.