Introduction
This project tries to simulate the property behaviors that exist in C# (and other languages...) in C++ without using any extensions. Most libraries or compilers that implement properties in C++ use extensions such as Managed C++ or C++ Builder or they use set
and get
methods which look like normal methods rather than properties.
Let us first see what a property is. A property acts like a field or member variable from the point of view of the library user, but it accesses the underlying variable through read
or write
methods or set
or ge
t methods.
For example, I have class A
and property Count
, I can write the following code.
A foo;
cout << foo.Count;
The Count
property actualy calls the get
function to return the value of the required variable. One of the main advantage of using properties instead of directly accessing the variable is that you can set the property as read only
(where you are only allowed to read the variable, not change it), write only
or both read write
, so let us implement it.
We need to be able to do the following.
int i = foo.Count;
foo.Count = i;
So it is obvious that we need to overload the =
operator for setting the variable and also the return type (which we will show a little later).
We will implement a class called property which will act exactly like a property, as the following example shows.
template<typename Container, typename ValueType, int nPropType>
class property {}
This Template class will represent our property. The Container
is the type of the class that will contain the variable, the set & get methods and the property. ValueType
is the type of the internal variable itself. nPropType
is the access type of the property (read only
, write only
or read write
).
So now we need to set a pointer to the set
and get
method from the container class to the property and also override the operator =
so that the property will act like the variable, so let's see the full listing of the property class.
#define READ_ONLY 1
#define WRITE_ONLY 2
#define READ_WRITE 3
template <typename Container, typename ValueType, int nPropType>
class property
{
public:
property()
{
m_cObject = NULL;
Set = NULL;
Get = NULL;
}
void setContainer(Container* cObject)
{
m_cObject = cObject;
}
void setter(void (Container::*pSet)(ValueType value))
{
if((nPropType == WRITE_ONLY) || (nPropType == READ_WRITE))
Set = pSet;
else
Set = NULL;
}
void getter(ValueType (Container::*pGet)())
{
if((nPropType == READ_ONLY) || (nPropType == READ_WRITE))
Get = pGet;
else
Get = NULL;
}
ValueType operator =(const ValueType& value)
{
assert(m_cObject != NULL);
assert(Set != NULL);
(m_cObject->*Set)(value);
return value;
}
operator ValueType()
{
assert(m_cObject != NULL);
assert(Get != NULL);
return (m_cObject->*Get)();
}
private:
Container* m_cObject;
void (Container::*Set)(ValueType value);
ValueType (Container::*Get)();};
So now let's examine each piece.
The following code just sets the Container
pointer to a valid instance that contains the property we're going to access.
void setContainer(Container* cObject)
{
m_cObject = cObject;
}
The following code sets a pointer to the get
and set
member functions of the container class. The only restrictions are that the set
member function must take a single parameter and return void and the get
member function must take no parameters but return the value type.
void setter(void (Container::*pSet)(ValueType value))
{
if((nPropType == WRITE_ONLY) || (nPropType == READ_WRITE))
Set = pSet;
else
Set = NULL;
}
void getter(ValueType (Container::*pGet)())
{
if((nPropType == READ_ONLY) || (nPropType == READ_WRITE))
Get = pGet;
else
Get = NULL;
}
The first operator in the following code is the =
operator, which calls the set member of the container passing the value. The second operator makes the entire property class act as the ValueType
so it returns the value returned by the get
function.
ValueType operator =(const ValueType& value)
{
assert(m_cObject != NULL);
assert(Set != NULL);
(m_cObject->*Set)(value);
return value;
}
operator ValueType()
{
assert(m_cObject != NULL);
assert(Get != NULL);
return (m_cObject->*Get)();
}
Now let's see how to use it.
As shown below, the PropTest
class implements a simple property called Count
. The actual value will be stored and retrieved from the private member variable m_nCount
, through the get
and set
methods. The get
and set
methods can have any name as long as their addresses are passed to the property class as shown in the constructor of the PropTest
object. The line property<PropTest,int,READ_WRITE> Count;
says that we have a read & write
property of type integer
in class PropTest
called Count
. Now you can call Count
as normal member variable even though you're really calling the set
and get
methods indirectly.
The initialization shown in the constructor of the PropTest
class is necessary for the property class to work.
class PropTest
{
public:
PropTest()
{
Count.setContainer(this);
Count.setter(&PropTest::setCount);
Count.getter(&PropTest::getCount);
}
int getCount()
{
return m_nCount;
}
void setCount(int nCount)
{
m_nCount = nCount;
}
property<PropTest,int,READ_WRITE> Count;
private:
int m_nCount;
};
As shown below you use the Count
property as though it were a normal variable.
int i = 5,
j;
PropTest test;
test.Count = i;
j = test.Count;
For read only
properties you create an instance of the property as follows
property<PropTest,int,READ_ONLY > Count;
And for write only
properties you would create an instance of the property as follows
property<PropTest,int,WRITE_ONLY > Count;
Note: If you set the property to read only and you try to assign to it, it will cause an assertion in debug builds. Similarly, if you set the property to write only and you try to read it an assertion will occur in debug builds.
Conclusion:
This shows how to implement properties in C++ using standard C++ without any extensions. Naturally, using set
and get
methods are more efficient because with this method you have a new instance of the property class per each property.