Adding properties to C++ 11 like C#

Posted in software by Christopher R. Wirz on Thu Feb 02 2012



C# offers both properties and fields. Fields are variables declared directly on a class (or struct). In higher level languages, fields are private and set/get methods are used to access these fields. C# offers members on the class called properties, which are the accessor methods. They can read, write, compute... all while promoting safety and flexibility.

Note: In C#, writing T get_Value() and void set_Value(T val) is the same as writing in C# as T Value {get; set;}.

First, let's take the basic example in C++


/**
* Demonstrating basic setting and getting
*/
class ExampleClass {
private:
    int _value1 = 1;

public:
    /**
    * The getter for value 1
    */
    int get_Value1() const { return this->_value1; }


    /**
    * The setter for value 1
    */
    void set_Value1(int val) { this->_value1 = val; }
};    

This has get and set methods, but how would we write this as a property?


/**
* Demonstrating property signature
*/
class ExampleClass {
private:
    int _value1 = 1;

public:        
    /**
    * The get/set property for value 1
    */
    property<int> Value1;    
};    

Unfortunately, we don't have this feature in C++. So we can add it! The first is the simple approach which just wraps a pointer:


/**
* The purpose of a property is to abstract how memory is accessed,
* so it makes sense it would wrap a pointer
*/
template <typename T>
class pointer_property {
private:
    // A private pointer to the actual data
    T* value;

public:
    /**
    * Constructor taking the pointer to the wrapped data
    *
    * @param ptr A pointer to the value
    */
    pointer_property(T* ptr) {
        value = ptr;
    }

    /**
    * Gets the data
    */
    operator T () {
        return *value;
    }

    /**
    * Sets the data
    */
    T operator = (const T &i) {
        return *value = i;
    }
};  

This basically fits the bill for a property: gives us the ability to put some logic around how values in memory are set. There is an alternative - and that is the use of C++11 functions and lambdas.


#include <stdio.h>
#include <functional>

/**
* The purpose of a property is to abstract how memory is accessed,
* with a lambda you can access almost anything that gets passed into its scope
*/
template <typename T>
class lambda_property {
private:
    std::function<void(const T&)> setFunction;
    std::function<T()> getFunction;

public:
    /**
    * Constructor taking the pointer to the wrapped data
    *
    * @param getf A function to set the value
    * @param setf A function to get the value
    */
    lambda_property(std::function<T()> getf, std::function<tvoid(const T&)> setf) {
        getFunction = getf;
        setFunction = setf;
    }

    /**
    * Gets the data
    */
    operator T () {
        return getFunction();
    }

    /**
    * Sets the data
    */
    T operator = (const T &i) {
        setFunction(i);
        return i;
    }
};

So why would I prefer one over the other? There are a few examples where it would make no difference:


/**
* An example class with private members
*/
class ExampleClass1 {
private:
    int _value1 = 1;
    int _value2 = 2;

public:
    
    /**
    * The Value property using  a pointer
    */
    pointer_property<int> Value1 = pointer_property<int>(&(this->_value1));

    /**
    * The Value property using a lambda
    */
    lambda_property<int> Value2 = lambda_property<int>([this]() { return this->_value2; }, [this](int val) {this->_value2 = val; });
};

/**
* A referenced structure containing data
*/
struct ExampleData{
    int Value1 = 1;
    int Value2 = 2;
};

/**
* An example class referencing data structure
*/
class ExampleClass2 {
private:
    ExampleData* _values = new ExampleData();

public:

    /**
    * The Value property using  a pointer
    */
    pointer_property<int> Value1 = pointer_property<int>(&(this->_values->Value1));

    /**
    * The Value property using a lambda
    */
    lambda_property<int> Value2 = lambda_property<int>([this]() { return this->_values->Value2; }, [this](int val) {this->_values->Value2 = val; });

	/**
	* Destructor
	*/
	~ExampleClass2() {
		if (_values == NULL) { return; }
		delete _values;
		_values = NULL;
	}
};

Seems fine. But here is where it all breaks down: what if the class is passed a pointer in a constructor?


class ExampleClass3 {
private:
    ExampleData* _values;

public:

    /**
    * Creates a new instance of the example class
    *
    * @param values The values wrapped by the class
    */
    ExampleClass3(ExampleData* values) {
        _values = values;
        // If you comment out this line, it breaks
        this->Value1 = pointer_property<int>(&(this->_values->Value1));
    };

    /**
    * The Value property using  a pointer
    */
    pointer_property<int> Value1 = pointer_property<int>(&(this->_values->Value1));

    /**
    * The Value property using a lambda
    */
    lambda_property<int> Value2 = lambda_property<int>(
        [this]() { return this->_values->Value2; }, 
        [this](int val) {this->_values->Value2 = val; }
    );
};   

So let's test the approaches:


int main()
{
    {
        ExampleClass* ex = new ExampleClass();
        printf("\nInitial:\n");
        printf("ex->get_Value1() = %d\n", (int)ex->get_Value1());
        ex->set_Value1(41);
        printf("\nAfter set:\n");
        printf("ex->get_Value1() = %d\n", (int)ex->get_Value1());
        delete ex;
    }
    {
        ExampleClass1* ex1 = new ExampleClass1();
        printf("\nInitial:\n");
        printf("ex1->Value1 = %d\n", (int)ex1->Value1);
        printf("ex1->Value2 = %d\n", (int)ex1->Value2);
        ex1->Value1 = 41;
        ex1->Value2 = 42;
        printf("\nAfter set:\n");
        printf("ex1->Value1 = %d\n", (int)ex1->Value1);
        printf("ex1->Value2 = %d\n", (int)ex1->Value2);
        delete ex1;
    }
    {
        ExampleClass2* ex2 = new ExampleClass2();
        printf("\nInitial:\n");
        printf("ex2->Value1 = %d\n", (int)ex2->Value1);
        printf("ex2->Value2 = %d\n", (int)ex2->Value2);
        ex2->Value1 = 41;
        ex2->Value2 = 42;
        printf("\nAfter set:\n");
        printf("ex2->Value1 = %d\n", (int)ex2->Value1);
        printf("ex2->Value2 = %d\n", (int)ex2->Value2);
        delete ex2;
    }
    {
        ExampleClass3* ex3 = new ExampleClass3(new ExampleData());
        printf("\nInitial:\n");
        printf("ex3->Value1 = %d\n", (int)ex3->Value1);
        printf("ex3->Value2 = %d\n", (int)ex3->Value2);
        ex3->Value1 = 41;
        ex3->Value2 = 42;
        printf("\nAfter set:\n");
        printf("ex3->Value1 = %d\n", (int)ex3->Value1);
        printf("ex3->Value2 = %d\n", (int)ex3->Value2);
        delete ex3;
    }

    return 0;
}

Now we can run the code:

Initial:                                                                                                                                                                             
ex->get_Value1() = 1                                                                                                                                                                 
                                                                                                                                                                                    
After set:                                                                                                                                                                           
ex->get_Value1() = 41                                                                                                                                                                
                                                                                                                                                                                    
Initial:                                                                                                                                                                             
ex1->Value1 = 1                                                                                                                                                                      
ex1->Value2 = 2                                                                                                                                                                      
                                                                                                                                                                                    
After set:                                                                                                                                                                           
ex1->Value1 = 41                                                                                                                                                                     
ex1->Value2 = 42                                                                                                                                                                     
                                                                                                                                                                                    
Initial:                                                                                                                                                                             
ex2->Value1 = 1                                                                                                                                                                      
ex2->Value2 = 2                                                                                                                                                                      
                                                                                                                                                                                    
After set:                                                                                                                                                                           
ex2->Value1 = 41                                                                                                                                                                     
ex2->Value2 = 42                                                                                                                                                                     
                                                                                                                                                                                    
Initial:                                                                                                                                                                             
ex3->Value1 = 1                                                                                                                                                                      
ex3->Value2 = 2                                                                                                                                                                      
                                                                                                                                                                                    
After set:                                                                                                                                                                           
ex3->Value1 = 41                                                                                                                                                                     
ex3->Value2 = 42 

While getting and setting values seems pretty much the same, using lambdas does offer the "laziness" that is one of the major advantages of properties.

What's next? You can try the example online.