Property in C ++

    Probably all fans of the C ++ language who used other languages, such as C #, are surprised: why are there no properties in the pluses? After all, this is really a convenient tool that allows you to fully control access to class members. Recently, I became interested in this issue. Thinking, looking through the Straustrup and finally googling, I came to the conclusion that property can be implemented using the language. I think many have already seen various implementations, for example, from microsoft , but for someone, I hope this will be an interesting discovery.
    In the article you will find one of the possible options for implementing properties using templates.

    Formulation of the problem


    To begin with, we decide what we want. But we want the property to be accessed as an ordinary field of an object through a dot (well, or ->), both for writing and reading, and without any brackets. That is, like this: In this case, the setter and getter functions defined by us should be called. I would also like for the property to be defined in the class as simple and clear as possible.

    a.property = value;
    MyClass var = a.property;



    Implementation


    We see that if a.property is an instance of the Property class that we defined, then we can overload the = operator to get the setter, as well as the cast operator, to get the getter. This class itself must be boilerplate to allow the use of properties of any type.
    In order not to implement a class for each property, overloaded operators must call the function using the pointer passed to the property in the constructor.
    For use, we declare a variable of our type Property as a public field in the class and pass the type that we need as the first parameter of the template, and pointers to functions in the parameters of the constructor.

    When all this was implemented, I remembered another great feature of the property: it can be Read Only. Prior to this, only a full-fledged property with a setter and getter was considered. And it is important that read and write permissions are checked at the compilation stage. Here, template specializations come to our aid. Three different classes can be implemented (read and write, read only, write only) Property with the same names, differing in the template parameter, the so-called specialization. The final implementation looks like this:
    
    #pragma once
    #define NULL 0
    class ReadOnly;
    class WriteOnly;
    class ReadWrite;
    template 
    class Property
    {
    };
    template 
    class Property
    {
    protected:
        typedef Type (Owner::*getter)();
        typedef void (Owner::*setter)(Type);
        Owner * m_owner;
        getter m_getter;
        setter m_setter;
    public:
        // Оператор приведения типа. Реализует геттер.
        operator Type()
        {
            return (m_owner->*m_getter)();
        }
        // Оператор присваивания. Реализует сеттер.
        void operator =(Type data)
        {
            (m_owner->*m_setter)(data);
        }
        Property() :
    		m_owner(NULL),
            m_getter(NULL),
            m_setter(NULL)
        {
        }
        Property(Owner * const owner, getter getmethod, setter setmethod) :
            m_owner(owner),
            m_getter(getmethod),
            m_setter(setmethod)
        {
        }
        void init(Owner * const owner, getter getmethod, setter setmethod)
        {
            m_owner = owner;
            m_getter = getmethod;
            m_setter = setmethod;
        }
    };
    template 
    class Property
    {
    protected:
        typedef Type (Owner::*getter)();
        Owner * m_owner;
        getter m_getter;
    public:
        // Оператор приведения типа. Реализует геттер.
        operator Type()
        {
            return (m_owner->*m_getter)();
        }
        Property() :
            m_owner(NULL),
            m_getter(NULL)
        {
        }
        Property(Owner * const owner, getter getmethod) :
            m_owner(owner),
            m_getter(getmethod)        
        {
        }
        void init(Owner * const owner, getter getmethod)
        {
            m_owner = owner;
            m_getter = getmethod;
        }
    };
    template 
    class Property
    {
    protected:
        typedef void (Owner::*setter)(Type);
        Owner * m_owner;
        setter m_setter;
    public:
        // Оператор присваивания. Реализует сеттер.
        void operator =(Type data)
        {
            (m_owner->*m_setter)(data);
        }
        Property() :
            m_owner(NULL),
            m_setter(NULL)
        {
        }
        Property(Owner * const owner, setter setmethod) :
            m_owner(owner),
            m_setter(setmethod)
        {
        }
        void init(Owner * const owner, setter setmethod)
        {
            m_owner = owner;
            m_setter = setmethod;
        }
    };
    


    Using


    You can use it like this:
    TestClass.h file
    
    #pragma once
    #include "Property.h"
    class TestClass
    {
    public:
    	TestClass(void);
    	~TestClass(void);
    	void _setterRW(int a);
    	int _getterRW();
    	Property testRW;
    	int _getterRO();
    	Property testRO;
    	void _setterWO(int a);
    	Property testWO;
    private:
    	int propRW;
    	int propRO;
    	int propWO;
    };
    


    File TestClass.cpp

    
    #include "TestClass.h"
    TestClass::TestClass(void)
    {
    	testRW.init(this, &TestClass::_getterRW, &TestClass::_setterRW);
    	testRO.init(this, &TestClass::_getterRO);
    	propRO = 123;
    	testWO.init(this, &TestClass::_setterWO);
    }
    int TestClass::_getterRW()
    {
    	return propRW;
    }
    void TestClass::_setterRW(int a)
    {
    	propRW = a; 
    }
    int TestClass::_getterRO()
    {
    	return propRO;
    }
    void TestClass::_setterWO(int a)
    {
    	propWO = a;
    }
    TestClass::~TestClass(void)
    {
    }
    


    File main.cpp
    
    #include "TestClass.h"
    #include "stdio.h"
    int main()
    {
    	TestClass t;
    	t.testRW = 15;
    	int a = t.testRW;
    	t.testWO = 34;
    	//a = t.testWO; //ошибка: чтение WriteOnly property
    	//t.testRO = 45; //ошибка: запись в ReadOnly property
    	printf("RW = %d\n", int(t.testRW));
    	printf("RO = %d\n", int(t.testRO));
    	scanf("%d", a);
    	return 0;
    }
    

    Conclusion


    This implementation suits me, but I do not like it for several reasons. The first is the need to specify the class to which the property belongs. The second is the need to initialize each property in the constructor, that is, at the execution stage. The third is the impossibility of creating a static property. Well, the fourth, less significant: the tip shows the Property type, and not what we write there, which may cause some questions from the one who first sees it. Also, the topic of pointers to class methods is very muddy, poorly disclosed and of little use. It is well explained here , but the most important thing is that I realized that you need to use them very carefully.
    It turned out not so intuitive to use as I wanted, but the goal was achieved.

    Used materials:
    www.rsdn.ru/article/vcpp/props.xml - the main ideas.
    www.rsdn.ru/forum/cpp/854559.1.aspx - an idea with access rights.
    rsdn.ru/article/cpp/fastdelegate.xml - a detailed explanation of pointers to functions and methods of classes in C ++.

    Sources with examples here .
    Visual Studio 2008 project here .

    This text is published under the CC-BY License .

    The original article can be found here . Copy-paste was made consciously, since the topic should be interesting to the habranography, and the site on which the article was originally published is unable to withstand even a small influx of visitors due to hosting.

    Also popular now: