Eric Fleegal's WebLog

. . . .

How to do Object Properties in C++

One of the many useful features of modern languages like C# are object properties, as they provide a higher level of encapsulation than public fields.  The field-like syntax is far easier to read and write than traditional C++ GetXXX and SetXXX functions. 

It’s surprising how many people don’t know that Visual C++ has properties too.  Microsoft added property fields into C++ as a language extension back in the days when COM programming was all the rage.  As with most C++ language extensions, the syntax is a bit clumsy; this one uses a Microsoft specific __declspec compiler directive.  The syntax is:

__declspec ( property ( get=nameOfGetFunction, put=nameOfSetFunction ) ) typeExpressing propertyName

When the compiler sees a data member declared with this attribute on the right of a member-selection operator ("." or "->"), it converts the operation to a get or put function, depending on whether such an expression is an l-value or an r-value. In more complicated contexts, such as "+=", a rewrite is performed by doing both get and put.  A property can also be declared read-only or write-only by specifying only the get or put function respectively. 

To make life a little easier, I introduce a header file “C++ Properties.h” with the following macros:

#define PROPERTY(TYPE, NAME) __declspec(property(get=Get##NAME,put=Set##NAME)) TYPE NAME

#define READONLY_PROPERTY(TYPE, NAME) __declspec(property(get=Get##NAME)) TYPE NAME

#define WRITEONLY_PROPERTY(TYPE, NAME) __declspec(property(put=Set##NAME)) TYPE NAME

Notice that these macros use preprocessor token pasting so that the get and put functions always map to GetXXX and SetXXX, where XXX is the name of the property.  This allows us to declare classes with properties in a very readable form; for example:

class GamePad

{

public:

        . . .

        READONLY_PROPERTY(bool, IsConnected);

        bool GetIsConnected() const;

        . . .

};

While not quite as elegant as the built in property syntax in C#, it’s not a bad substitute.

You can declare virtual C++ properties simply by making the getter and/or setter methods virtual.  Similarly, abstract properties can be defined by using pure virtual getter and/or setter methods.  For example:

class GamePad

{

public:

        . . .

        READONLY_PROPERTY(bool, IsConnected);  // virtual property

        virtual bool GetIsConnected() const;

        READONLY_PROPERTY(float, PollingRate);  // abstract property

        virtual float GetPollingRate() const = 0;

        . . .

};

Note that the const semantics for the property are determined by the getter or setter method.

Array semantics are also supported. The syntax is basically the same, but with an added “[]”, as follows:

__declspec ( property ( get=nameOfGetFunction, put=nameOfSetFunction ) ) typeExpressing propertyName[]

The accessor function simply needs to take an index argument.  Although we can use our existing macros for arrays, as follows

class GamePad

{

public:

        . . .

        READONLY_PROPERTY(ButtonState, Buttons)[];

        ButtonState GetButtons(size_t buttonIndex);

        . . .

};

// And used like:

GamePad gamePad;

. . .

ButtonState buttonState = gamepad.Buttons[3];

I find it somewhat less confusing to have additional macros in “C++ Properties.h”

#define ARRAY_PROPERTY(TYPE, NAME) __declspec(property(get=Get##NAME,put=Set##NAME)) TYPE NAME[]

#define READONLY_ARRAY_PROPERTY(TYPE, NAME) __declspec(property(get=Get##NAME)) TYPE NAME[]

#define WRITEONLY_ARRAY_PROPERTY(TYPE, NAME) __declspec(property(put=Set##NAME)) TYPE NAME[]

Changing the above class into:

class GamePad

{

public:

        . . .

        READONLY_ARRAY_PROPERTY(ButtonState, Buttons);

        ButtonState GetButtons(size_t buttonIndex);

        . . .

};

The array access functions can also be multidimensional:

class Picture

{

public:

        . . .

        READONLY_ARRAY_PROPERTY(Color, Pixels);

        Color GetPixels(unsigned int x, unsigned int y);

        . . .

};

// And used like:

Picture picture;

. . .

Color colorAt = picture.Pixels[x][y];

Because Properties provide a higher level of encapsulation than public fields, I often find myself exposing private fields through const properties.

class GamePad

{

public:

        . . .

        READONLY_PROPERTY(bool, IsConnected);

        bool GetIsConnected() const { return isConnected_; }

        . . .

private:

        bool isConnected_;

};

Just like traditional accessor functions, this enables internal members to change the isConnected_ state while exposing the state to the public scope as a const property.  This pattern is so very common that I introduce an explicit property macro for it:

#define READONLY_PROPERTY_RVALUE(TYPE, NAME, RVALUE_EXPR) \

__declspec(property(get=Get##NAME)) TYPE NAME; \

TYPE Get##NAME() const { return RVALUE_EXPR; }

For symmetry I also add the following two macros, though admittedly they’re rarely used (and many of my colleagues hate them).

#define PROPERTY_VALUE(TYPE, NAME, RVALUE_EXPR, LVALUE_EXPR) \

__declspec(property(get=Get##NAME,put=Set##NAME)) TYPE NAME; \

TYPE Get##NAME() const { return RVALUE_EXPR; } \

void Set##NAME(TYPE newValue) { LVALUE_EXPR = newValue; }

#define WRITEONLY_PROPERTY_LVALUE(TYPE, NAME, LVALUE_EXPR) \

__declspec(property(put=Set##NAME)) TYPE NAME; \

void Set##NAME(TYPE newValue) { LVALUE_EXPR = newValue; }

Although it would preferable for C++ properties to have a cleaner built-in syntax, these macros provide enough of an abstraction to enable use of properties without sacrificing readability.

 

Published Thursday, April 17, 2008 3:03 PM by ericflee
Filed under:

Comments

No Comments
Anonymous comments are disabled

© 2008 Microsoft Corporation. All rights reserved. Terms of Use  |  Trademarks  |  Privacy Statement
Microsoft
Page view tracker