Introduction
What this article presents is implementing in C++ the C# functionality for both Property and Indexer and controlling their compile-time accessibility using accessor-modifiers.
The provided source code contains a generic framework for performing both Property and Indexer in C++. The only thing that the user of this framework needs to implement is an Accessor functor, which requires set
and get
accessor functions. The framework provides pre-defined properties, each with different accessibility options, which will wrap around the user-defined Accessor functor. Thereby, to declare a property within a Host class is simply choosing a property based upon expected accessibility and assigning it with an Accessor.
Properties are known as smart fields, and enable access to member variables of a class, but maintains encapsulation by implicit access to its set
and get
accessors.
Indexers are also called smart arrays in C#, and can be used to use an object as an array. Similar to C# Properties, the subscript ([]
) operator has access to its own set
and get
accessors.
Doxygen generated source code is available here: Documentation.
Background
My programming lingua franca has been C++ for quite a while, and thereby it is my foundation for other programming languages like Java and C#. Every time I see some feature with these other languages, I am curious enough to query if that feature could be implemented in C++. C# Properties and Indexers is a case in point.
In CodeProject, I have read several articles on C++ implementation of C# Property, and none pertaining to C# Indexer. However, I did not find an article that provided compile-time check of declared accessibility of a Property's field.
The code presented in this article provides Property and Indexer functionality in C++, including compile-time check of declared accessibility of the assigned accessor-modifier for set
and get
accessors.
C++ Property Layout
Collaboration Model
In this C++ implementation of C# Property, there are three components:
- Accessor defines how values are managed by the
set
or get
accessor member functions. The value can either reside within the scope of the Accessor or within the scope of its friend
Host. - Property is a container of a single reference to an Accessor, and it defines the access scope (
public
or private
) to the Accessor's set
and get
member functions through its access functions (i.e., assignment and cast operators). - Host is a container which may reference one or more Properties. Host is a
friend
of both Property and Accessor. The purpose of this friendship is if either of the Property access functions (i.e., assignment and cast operators) are declared private
to those outside the scope of the Host class, the Host can still access the value within its internal scope, directly through its referenced Property, or indirectly through the Property's referenced Accessor.
Call Model
Listed here is the call model of this C++ implementation, from assigning to acquiring values through a C# Property.
- Value assignments to a Property field are performed through its assignment operator.
- If the access-modifier for this operator is
public
, then any entity outside the scope of the Host class can make the property's value assignments. - If the access-modifier for this operator is
private
, then only the Host can make the property's value assignments.
- All Property value assignments are passed to the Accessor's
public
set
member function. - The
set
accessor passes the value to any assigned resource. The assigned resource can either be within the scope of the Accessor or within the scope of its friend
Host. - Value retrievals from a property field are performed through a cast operator.
- If the access-modifier for this operator is
public
, then any entity outside the scope of the Host class can request the property's value. - If the access-modifier for this operator is
private
, then only the Host can request the property's value.
- All Property value requests are passed to the Accessor's
public
get
member function. - The
get
accessor requests a value from any assigned resource. As with the set
accessor, the assigned resource can either be within the scope of the Accessor or within the scope of its friend
Host.
Accessor Interface and Base Classes
Every Accessor for a property must define the following functions:
- A
get
property accessor is used to return the property value. - A
set
property accessor is used to assign a new value.
The interface IAccessor
defines the expected accessor functions, and the abstract base class AccessorBase
holds the ValueType
value shared by the accessor functions.
template <class Host, typename ValueType>
class IAccessor
{
public:
virtual void set(Host*, const ValueType) = 0;
virtual ValueType get(Host*) const = 0;
};
template <class Host, typename ValueType>
class AccessorBase : public IAccessor<Host, ValueType>
{
public:
typedef ValueType value_type;
};
Accessor Implementation Example
The class Number_Accessor
is a derivation of the AccessorBase
class, and has a ValueType
of int
. In this example implementation, the value reference for the set
and get
accessors is within the scope of this class: private int m_value
.
template <class Host>
class Number_Accessor : public AccessorBase<Host, int>
{
friend Host;
public:
void set(Host * , const int _value)
{ m_value = _value; }
int get(Host * ) const
{ return m_value; }
private:
int m_value;
};
Property Interface and Base Class
The abstract class PropertyBase
holds an instance of the Accessor container. PropertyBase
is abstract because it does not hold implementations of its derived interface IProperty
.
template<typename ValueType>
class IProperty
{
virtual ValueType operator =(const ValueType& value) = 0;
virtual operator ValueType() const = 0;
};
template<class Host, class Accessor, typename ValueType>
class PropertyBase : public IProperty<ValueType>
{
public:
PropertyBase(Host *_pHost) : m_pHost( _pHost )
{}
ValueType operator =(const ValueType& _value)
{
m_Accessor.set(m_pHost, _value);
return _value;
}
operator ValueType() const
{ return m_Accessor.get(m_pHost); }
protected:
Host* m_pHost;
Accessor m_Accessor;
};
Properties with Different Access Modifiers
There are three different Property classes, and each has a different arrangement of access-modifiers to their setter and getter functions:
Property
: setter and getter functionality are both public
. Property_Set_Public
: setter is public
and getter is private
. Property_Get_Public
: setter is private
and getter is public
.
Notice that the access modifiers for the accessor methods of the class Property
are both public
:
template<class Host, class Accessor, typename ValueType>
class Property : public PropertyBase<Host, Accessor, ValueType>
{
public:
Property(Host *_pHost = NULL) : PropertyBase( _pHost ){}
public:
using PropertyBase<Host, Accessor, ValueType>::operator =;
public:
using PropertyBase<Host, Accessor, ValueType>::operator ValueType;
};
Host Using C++ Property Implementation
Understand that the purpose of the article is to provide a generic toolkit (a framework) for performing a Property within a C++ class, the only thing that the user needs to implement an Accessor.
Define Accessor
To define an Accessor, derive a class from AccessorBase
and implement the expected set
and get
accessor functions.
Here, an accessor Number_Accessor
is derived from TAccessor
, which encapsulates the scope of the accessor's value of type int
within.
template <class Host, typename ValueType>
struct TAccessor : public AccessorBase<Host, ValueType>
{
void set(Host * , const ValueType _value)
{ m_value = _value; }
ValueType get(Host * ) const
{ return m_value; }
protected:
ValueType m_value;
};
template <class Host>
struct Number_Accessor : public TAccessor<Host, int> {};
Define Properties for Host
Within a Host class, each property is defined (typedef
) with its expected accessibility and accessor.
To define a Property within this framework, select one of these Property classes based upon the accessibility needs: Property
, Property_Set_Public
, or Property_Get_Public
, and assign it the expected Accessor it will represent.
In the following coding example, the class Test
has defined three different Properties (each with a different accessibility scope) and all using the same accessor Number_Accessor
.
class Test
{
typedef ::Property<
::Test,
::Number_Accessor<::Test>,
::Number_Accessor<::Test>::value_type> Property_Number;
typedef ::Property_Set_Public<
::Test,
::Number_Accessor<::Test>,
::Number_Accessor<::Test>::value_type> Property_Set_Public_Number;
typedef ::Property_Get_Public<
::Test,
::Number_Accessor<::Test>,
::Number_Accessor<::Test>::value_type> Property_Get_Public_Number;
public:
Property_Number Number_A;
Property_Set_Public_Number Number_B;
Property_Get_Public_Number Number_C;
};
Using Host with Properties
When actually using the just implemented class Test
, at compile-time, you can see the setters and getters of each of the three properties (Number_A
, Number_B
, and Number_C
) which are accessible outside the scope of their Host class. Referencing the following code, notice where the compile-time errors are occurring for each of the restrictive access Properties.
void main()
{
::Test test;
int i = 0;
{
test.Number_A = 10;
i = test.Number_A;
}
{
test.Number_B = 10;
}
{
i = test.Number_C;
}
}
C++ Indexer Layout
Collaboration Model
In this C++ implementation of the C# Indexer, there are three components for providing accessor functionality through a Host's subscript operator.
- IndexerAccessor is the same as a Property's Accessor, except the
set
and get
accessors are passed the index value provided though the called subscript operator. - Indexer is the same as a Property.
- Host may define at most one subscript operator. When a Host's subscript operator is used, it passes out an instance of a derived Indexer. Depending upon the usage of the subscript operator, assigning values is performed by the Indexer's assignment operator, and acquiring values is performed by the Indexer's cast operator.
Call Model
The call model for the C++ implementation of a C# Indexer is essentially the same as the C# Property, except the Host's subscript operator usage is made indirectly through an instance of a derived Indexer. What is occurring is that the Indexer is being used as a functor, i.e., simply any object that is called as if it is a function providing access to the Accessor.
IndexerAccessor Interface and Base Classes
The only difference between the interface IIndexerAccessor
and the interface IAccessor
is that the set
and get
accessor member functions require an additional parameter (index) which is provided upon usage of the Host's subscript operator.
template <class Host, typename ValueType>
class IIndexerAccessor
{
public:
virtual void set(Host*, const int _index, const ValueType) = 0;
virtual ValueType get(Host*, const int _index) const = 0;
};
template <class Host, typename ValueType>
class IndexerAccessorBase : public IIndexerAccessor<Host, ValueType>
{
public:
typedef ValueType value_type;
};
IndexerAccessor Implementation Example
The class Number_Indexer_Accessor
is a derivation of the class IndexerAccessorBase
, and has a ValueType
of int
. In this example implementation, the value reference for the set
and get
accessors is not within the scope of this class but within the scope of its Host: private int *m_ptrArray
.
template <class Host>
class Numbers_Indexer_Accessor : public IndexerAccessorBase<Host, int>
{
friend Host;
public:
void set(Host* _pHost, const int _index, const int _value)
{ _pHost->m_ptrArray[_index] = _value; }
int get(Host* _pHost, const int _index) const
{ return _pHost->m_ptrArray[_index]; }
};
Indexers Interface and Base Class
The abstract class IndexerBase
holds an instance the IndexerAccessor
container. IndexerBase
is abstract because it does not hold implementations of its derived interface IIndexer
.
Indexers with Different Access Modifiers
As with Properties, there are three different Indexers, and each has a different arrangement of access-modifiers to their setter and getter functions:
Inderer
: setter and getter functionality are both public
. Indexer_Set_Public
: setter is public
and getter is private
. Indexer_Get_Public
: setter is private
and getter is public
.
Notice that the access modifiers for the accessor methods of the class Indexer
are both public
:
template<class Host, class IndexAccessor, typename ValueType>
class Indexer : public IndexerBase<Host,IndexAccessor,ValueType>
{
public:
Indexer(Host *_pHost, int _index = 0) : IndexerBase( _pHost, _index)
{}
public:
using IndexerBase<Host,IndexAccessor,ValueType>::operator =;
public:
using IndexerBase<Host,IndexAccessor,ValueType>::operator ValueType;
};
Host Using the C++ Indexer Implementation
This is a simple example using the class Indexer
with the previously implemented IndexAccessor
labeled Numbers_Indexer_Accessor
.
Host's Subscript Operator and Indexer
The class Host_Numbers_Indexer
defines an Indexer using a Numbers_Indexer_Accessor
labeled Number_Indexer
. Every time the Host's subscript operator is called (for either assigning or acquiring values), an instance of Numbers_Indexer
is provided, and acts as a functor for setter and getter operations.
And most importantly, notice in particular what is returned from the subscript operator of Host_Number_Indexer
; which is an Indexer
derived Numbers_Indexer
.
class Host_Numbers_Indexer
{
friend ::Numbers_Indexer_Accessor<::Host_Numbers_Indexer>;
friend ostream &operator<<( ostream &, const Host_Numbers_Indexer & );
typedef ::Indexer <
::Host_Numbers_Indexer,
::Numbers_Indexer_Accessor<::Host_Numbers_Indexer>,
::Numbers_Indexer_Accessor<::Host_Numbers_Indexer>::value_type >
Numbers_Indexer ;
public:
Host_Numbers_Indexer(int _sizeArray = 10)
{
m_sizeArray = ( _sizeArray > 0 ? _sizeArray : 10 );
m_ptrArray = new int[ m_sizeArray ]; assert( m_ptrArray != 0 ); for ( int i = 0; i < static_cast<int>(m_sizeArray); i++ ) {
m_ptrArray[ i ] = i ;
}
}
Host_Numbers_Indexer::Numbers_Indexer operator[]( int _index )
{ return Numbers_Indexer(this, _index); }
private:
int *m_ptrArray; size_t m_sizeArray; };
Using the Host's Indexer through the Subscript Operator
Referring to the following code, upon creating an instance of the class Host_Numbers_Indexer
, it populates a private array with ten integers. The initial contents of this array are dumped. Then, through the Host's subscript operator, the Indexer passes in the integer 30 into the sixth position of the array through the IndexAccessor
, and then reads it back. The modified contents of this array are dumped again.
Host_Numbers_Indexer test;
cout << "Before input: " << endl
<< "test: " << endl << test << endl;
test[5] = 30;
cout << test[5] << endl;
cout << "After input: " << endl
<< "test: " << endl << test << endl;
Output
This is the console output of the aforementioned code.
Before input:
test:
0 1 4 9
16 25 36 49
64 81
30
After input:
test:
0 1 4 9
16 30 36 49
64 81
Conclusion
By reverse engineering the C# Property and Indexer, I have gained an even greater appreciation in the flexibility that C++ provides. Although it maybe very straightforward to use these features in C#, I found this exercise in putting together this code in C++ very engaging.