A C++ implementation of the C# functionality for both Property and Indexer, and controlling their compile-time accessibility using accessor-modifiers.

Please Sign up or sign in to vote.

C++ implementation of the C# Property and Indexer with Accessor-Modifiers

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.

, 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.

, 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.

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.