<

Still celebrating National IT Professionals Day with 3 months of free Premium Membership. Use Code ITDAY17

x

How to add properties to standard C++ classes

Published on
16,221 Points
5,921 Views
8 Endorsements
Last Modified:
Awarded
evilrix
An expert in cross-platform ANSI C/C++ development, specialising in meta-template programming and low latency scalable architecture design.

C++ Properties


One feature missing from standard C++ that you will find in many other Object Oriented Programming languages is something called a Property. These are like data members except they can have preconditions imposed on them prior to getting or setting their value. In C++ the general way to implement property like behaviour is to have a getter and/or setter member function. For the most part this suffices but there is an issue with this approach: you lose the syntax and semantics of a data member and, instead, have to deal with the syntax and semantics of a member function.

What do we mean by this? Let's take a very simple example class called "account" that contains an int called "balance_".

class account
{
public:
   int balance_;
};

Open in new window


As it currently stands "balance_" is a public data member. Although this gives us access to "balance_" it's uncontrolled - no preconditions can be imposed. This is bad OOP design. It means "account" has no control over the value of "balance_" and so cannot guarantee the value is sane. In other words we could set balance_ to any rogue value that may or may not be appropriate for what it represents. Let's make a change to ensure this is no longer the case.

class account
{
private:
   int balance_;
};

Open in new window


That's it, now "balance_" is private so only "account" can change it. Of course, this isn't much use if we do want the outside world to change the value of "balance_". What we need is a way to get and set the value but in a way that "account" can ensure things are sane. Enter the getter and setter function.

class account
{
public:
   int get() const
   {
      return balance_;
   }
   
   void set(int balance)
   {
      balance_ = balance;
   }
private:
   int balance_;
};

Open in new window


Now we have functions getting and setting "balance_", which means we can put additional code in there to ensure, for example, that when we set "balance_" it cannot be negative (no one wants one of those!). Let's do just that.

class account
{
public:
   int get_val() const
   {
      return balance_;
   }
   
   void set_val(int balance)
   {
      if(balance < 0)         
      {
         // ERROR!
      }
         
      balance_ = balance;
   }
private:
   int balance_;
};

Open in new window


Great, finally a class that contains a member that we can get and set but in a controlled way. It's all good right? Well, yes and no. You see we are now stuck with using function syntax and semantics. This presents two issues:

1. Syntax: When writing generic code we need to rely on a class implementing a get or set method; if it doesn't the code won't build.
2. Semantics: You can't freely use get and set functions in an expression.

Let's deal with syntax first. We are going to write a generic function that takes an object that models the pair concept and sets both their values (first and second) to a value.

template <typename pairT>
void func(pairT & mypair)
{
   mypair.first = 320;
   mypair.second = 240;
}

Open in new window


This will work very well with std::pair.

std::pair<int, int> mypair;
func(mypair);

Open in new window


Let's say we wanted to implement our own pair object so we can implement some sanity checks. We could do this by aggregating std::pair.

class foo_pair
{
public:
   int get_first() const
   {
      return mypair_.first;
   }
   
   int get_second() const
   {
      return mypair_.second;
   }

   void set_first((int val)
   {
      if(val < 1 || val > 100)
         throw std::invalid_argument(
            "value must be between 1 and 100"
         );
         
      mypair_.first = val;
   }

   void set_second((int val)
   {
      if(val < 1 || val > 100)
         throw std::invalid_argument(
            "value must be between 1 and 100"
         );
         
      mypair_.first = val;
   }
private:
   std::pair<int, int> mypair_;
};

Open in new window


Now, let's try using this code with our function...

foo_pair mypair;
func(mypair);

Open in new window


What's happened here? Simple. It won't compile because the function doesn't know it has to call set_first and set_second. Major fail! :(

Now, let's take a look at the issue of Semantics. Consider this small code expression...

int x;
int y;
int z = x = y = 10;

Open in new window


If x and y were objects that represented special ints (a bounded int maybe) that implemented a set method. Would this still work? Of course not

int x;
int y;
int z = x.set(10) = y.set(10); // this makes no sense 

Open in new window


Let see what we must do to make it work.

foo x;
foo y;
x.set(10);
y.set(10);
int z = 10;

Open in new window


Notice how we are forced to use functions, which breaks the nice free semantics of using assignment operators? Even if we make set return the value it has just set this still doesn't work very well.

foo x;
foo y;
int z = x.set(y.set(10));

Open in new window


It's just ugly and non-intuitive.

I hope that I've managed to demonstrate that although the getter and setter functions do serve a purpose they are a poor replacement for direct access to a variable. At this point I hope you are wondering, "great, how would a property help us"? Let's find out!

First, to give us a starting point, let's take a look at how a property could be defined using a language that has native support. We will use C# and re-implement the foo_pair class.

class foo_pair
{
public:
   int first
   {
      get
      {
         return first_;
      }
      
      set
      {
         if(value < 1 || value > 100)         
         {
            // ERROR!
         }
            
         first_ = value;
      }
   }
   
   int second
   {
      get
      {
         return second_;
      }
      
      set
      {
         if(value < 1 || value > 100)         
         {
            // ERROR!
         }
            
         second_ = value;
      }
   }
   
private:
   int first_;
   int second_;
};

Open in new window


How neat is that? This implements a "first" and "second" property. Notice how each property has a get and set clause? You can implement either or both of these to make a property read/write, read-only or write-only. In this case we've implemented both so the property is read/write. Implementing only get makes it read-only and implementing only set makes it write only. As you can see the set clause can perform validation. Notice there is no function syntax so there is no actual value passed in? Instead C# provide access to a special implicit variable call "value", which will contain the actual value being set. The property allows for the sanity checks whilst preserving data member syntax and semantics.

So, the question is, can we do this in C++? The answer is sort of. The fact is there is no specific native support for properties in standard C++ (although some compilers to have vendor specific extensions) but the language does give us the tools we need to implement our own. Of course, we will never get the nice neat solution like we have in C# because properties are not part of the C++ language but we can get a reasonable facsimile. Let's look at the code that will allow us to implement properties in C++.

#define property_(TYPE, OWNR, NAME, IMPL) \
   private: \
      class NAME ## __ \
      { \
      friend class OWNR; \
      public: \
         typedef NAME ## __ this_type; \
         typedef TYPE value_type; \
         NAME ## __ () {} \
         explicit NAME ## __ (value_type const & value) \
            : NAME ## (value) {} \
         IMPL \
      private: \
         value_type NAME ##; \
      }; \
   public: \
      NAME ## __ NAME;

#define get_ \
   operator value_type const & () const \
   { \
      return get(); \
   } \
   value_type const & get() const

#define set_ \
   this_type & operator = (value_type const & value) \
   { \
      set(value); \
      return *this; \
   } \
   void set(value_type const & value)

#define xprop_(NAME) \
   NAME ## . ## NAME

Open in new window


Pretty scary eh? That's ok - it's macros and they normally are! This article isn't going to teach you how to read or write macros but it's not that hard and a good tutorial will explain all you need to know to follow this code.

So, how does this work? We then have four macros to facilitate adding a property class to a host class. All we do is use these macros to both define and implement a property.

Let's take a look at what each macro does:

property_ : this macro defines a property, which is really nothing more than a local class with the cast and assignment operators defined to call 'get' and 'set' respectively

This macro takes the following parameters:
* TYPE - this is the type of the property (for example int)
* OWNR - the host (parent) class name, required to be made a friend of the property allowing unprotected access
* NAME - this is the name of the property (for example "first" or "second" as per the example above)
* IMPL - this is the code that defines get and/or set, use add either or both of the get_ and set_ macros here
   
get_   : this macro defines the non-const get clause - you need to add your own logic to specialise its behaviour between a { and a }

set_   : this macro defines the set clause - you need to add your own logic to specialise its behaviour between a { and a }

xprop_ : this macro allows the parent class to access the raw value unchecked variable bypassing the get and set methods

The astute reader may note that some operator semantics such as ++ and += will not work (my thanks to EE's Dan Rollins for pointing that out). These can be added after the get and set macros in the property definition, as required. When you add them make sure they call set to ensure proper value validation. We could trivialise this by defining additional macros for the operators we wish to support. I'll leave that as an exercise for the reader.

The solution presented here tries to find a balance between simplicity and safety to provide property semantics - without proper language support there will always be a compromise we will need to make.

That's it - pretty simple really. Here is the canonical form for using these macros to add a property to a class.

#include <property.hpp>

class foo_pair
{
public:
   foo_pair() :
      first(9), second(5) // initilising properties (optional, if not used they will default construct)
      {}

   // Implement get, set and ++ operators
   property_(

      int, foo_pair, first, // int foo_pair::first

      get_
      {
         return first;
      }

      set_
      {
         if(value < 1 || value > 100)         
         {
            // ERROR!
         }

         first = value;
      }

      this_type & operator ++() { set(first +1); return *this; }

      this_type operator ++(int)
      {
         this_type tmp(first);
         set(first +1);
         return tmp;
      }

   );

   // Implement get, set and += operator
   property_(

      int, foo_pair, second, // int foo_pair::second

      get_
      {
         return second;
      }

      set_
      {
         if(value < 1 || value > 100)         
         {
            // ERROR!
         }

         second = value;
      }

      this_type operator += ( int rhs )
      {
         set(second + rhs);
         return *this;
      }

   );

private:
   void bypass() // for internal use by foo_pair only!
   {
      // accessing property members, circumventing set and get
      xprop_(first) = 0;
      xprop_(second) = 100;
   }
};

template <typename pairT>
void func(pairT & mypair)
{
   mypair.first = 320;
   mypair.second = 240;
}

int main()
{
   foo_pair mypair;

   func(mypair); // Look, we can now call the generic function

   int z = mypair.first = mypair.second; // Look, we can now perform expressions using variable and not function syntax

   // Some additional operator manipulation
   mypair.first++;
   ++mypair.first;
   mypair.second+=10;
}

Open in new window


So, that's it. With a few simple macros we've managed to add properties to C++. Ok, in reality using some macro trickery we're just adding member classes with some specialised behaviour but in reality this is more or less what a property is anyway. These simple macros just simplify the process of adding the necessary boilerplate to the class to implement the property.

Is there a cost of using this code? Not really. Everything is passed around as a reference and the size of the property class should be no larger than the variable it represents since it has no other members. As there is no dynamic polymorphism involved the compiler should be able to inline most (if not all) of this and, thus, optimise away the function calls involved. Of course, if the get or set are too complex this may not be the case but, then, this would be no different from implementing get or set function anyway.

Even if you decide using this code isn't for you, I do hope reading this article has, at least, given you an insight into another aspect of OOP development and design - one that is, sadly, missing from the current version (C++03) of C++.
8
Comment
Author:evilrix
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
2 Comments
 
LVL 49

Expert Comment

by:DanRollins
Good article!  Got my YES vote!
0
 
LVL 40

Author Comment

by:evilrix
Thank you Dan.
0

Featured Post

New feature and membership benefit!

New feature! Upgrade and increase expert visibility of your issues with Priority Questions.

Join & Write a Comment

The viewer will learn how to user default arguments when defining functions. This method of defining functions will be contrasted with the non-default-argument of defining functions.
The viewer will learn additional member functions of the vector class. Specifically, the capacity and swap member functions will be introduced.

Keep in touch with Experts Exchange

Tech news and trends delivered to your inbox every month