How to add properties to standard C++ classes

AID: 3843
  • Status: Published

7830 points

  • Byevilrix
  • TypeTutorial
  • Posted on2010-09-29 at 13:09:51
Awards
  • Community Pick
  • Experts Exchange Approved

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_;
};
                                    
1:
2:
3:
4:
5:

Select allOpen 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_;
};
                                    
1:
2:
3:
4:
5:

Select allOpen 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_;
};
                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:

Select allOpen 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_;
};
                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:

Select allOpen 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;
}
                                    
1:
2:
3:
4:
5:
6:

Select allOpen in new window



This will work very well with std::pair.

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

Select allOpen 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_;
};
                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:

Select allOpen in new window



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

foo_pair mypair;
func(mypair);
                                    
1:
2:

Select allOpen 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;
                                    
1:
2:
3:

Select allOpen 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 
                                    
1:
2:
3:

Select allOpen 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;
                                    
1:
2:
3:
4:
5:

Select allOpen 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));
                                    
1:
2:
3:

Select allOpen 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_;
};
                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:

Select allOpen 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
                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:

Select allOpen 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;
}
                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:

Select allOpen 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++.
Asked On
2010-09-29 at 13:09:51ID3843
Tags

c++ properties

Topic

C++ Programming Language

Views
2391

Comments

Expert Comment

by: DanRollins on 2010-10-30 at 16:12:13ID: 20954

Good article!  Got my YES vote!

Author Comment

by: evilrix on 2010-10-30 at 16:38:45ID: 20955

Thank you Dan.

Add your Comment

Please Sign up or Log in to comment on this article.

Join Experts Exchange Today

Gain Access to all our Tech Resources

Get personalized answers

Ask unlimited questions

Access Proven Solutions

Search 3.2 million solutions

Read In-Depth How-To Guides

1000+ articles, demos, & tips

Watch Step by Step Tutorials

Learn direct from top tech pros

And Much More!

Your complete tech resource

See Plans and Pricing

30-day free trial. Register in 60 seconds.

Loading Advertisement...

Top C++ Experts

  1. jkr

    261,219

    Guru

    1,000 points yesterday

    Profile
    Rank: Savant
  2. sarabande

    121,084

    Master

    3,800 points yesterday

    Profile
    Rank: Sage
  3. Infinity08

    54,855

    Master

    0 points yesterday

    Profile
    Rank: Genius
  4. ambience

    50,164

    Master

    0 points yesterday

    Profile
    Rank: Sage
  5. Zoppo

    48,382

    0 points yesterday

    Profile
    Rank: Genius
  6. evilrix

    48,358

    80 points yesterday

    Profile
    Rank: Genius
  7. satsumo

    22,400

    0 points yesterday

    Profile
    Rank: Guru
  8. tampnic

    19,040

    0 points yesterday

    Profile
    Rank: Master
  9. phoffric

    16,596

    0 points yesterday

    Profile
    Rank: Genius
  10. DanRollins

    16,330

    0 points yesterday

    Profile
    Rank: Genius
  11. duncan_roe

    14,400

    0 points yesterday

    Profile
    Rank: Genius
  12. gtokas

    12,700

    0 points yesterday

    Profile
    Rank: Wizard
  13. AndyAinscow

    12,132

    0 points yesterday

    Profile
    Rank: Genius
  14. mccarl

    9,600

    0 points yesterday

    Profile
    Rank: Wizard
  15. TommySzalapski

    8,800

    0 points yesterday

    Profile
    Rank: Genius
  16. pepr

    7,824

    0 points yesterday

    Profile
    Rank: Genius
  17. kaufmed

    7,168

    0 points yesterday

    Profile
    Rank: Genius
  18. Thommy

    6,700

    0 points yesterday

    Profile
    Rank: Wizard
  19. ubound

    6,550

    0 points yesterday

    Profile
    Rank: Master
  20. kuroji

    6,000

    0 points yesterday

    Profile
  21. for_yan

    6,000

    0 points yesterday

    Profile
    Rank: Genius
  22. mrwad99

    4,600

    0 points yesterday

    Profile
    Rank: Wizard
  23. masheik

    4,572

    0 points yesterday

    Profile
    Rank: Guru
  24. Orcbighter

    4,332

    0 points yesterday

    Profile
    Rank: Master
  25. ozo

    4,300

    0 points yesterday

    Profile
    Rank: Savant

Hall Of Fame