DevTalk.net

ActiveMesa's software development blog. MathSharp, R2P, X2C and more!

Property Change Notifications in C++

without comments

Having spent a lot of time in .NET land, I find it difficult to adapt to the lack of first-class support for certain commonplace concepts in C++. One problem I had recently is change notifications: an ability for a property to notify whoever is interested that its value has been changed. C++ does not support this because

  • There is no concept of a ‘property’ in standard C++ (fields exist, of course).
  • There is no concept of an ‘event’ in C++ either.

Luckily, both of these problems can be successfully solved – either by using a library or – more contentiously – a compiler extension. Let’s take a look.

Properties

A property in a language like C# is simply a wrapper for accessor and mutator methods (a.k.a. getter and setter) methods. For example, in C# you can write

bool CanVote
{
  get { return age >= 16; }
}

And this could be used as person.CanVote, i.e. as if we were accessing a field-like construct. Under the covers, of course, this turns into a method (that’s what they call functions in C#/Java), but that’s just compiler magic.

In C++, apart from the (obvious) way of making an accessor function, there’s no support for properties. Unless, of course, you are using a compiler from Microsoft or Intel. This extension is called __declspec(property) and what it does is… properties! Yep, properties in C++.

So here’s how it works: you create accessor/mutator functions as always but then create a field-like declaration with __declspec(property) prefix:

class Person
{
public:
  int age;
  int get_age() const { return age; }
  void put_age(int value)
  {
    if (age == value) return;
    age = value;
    // change notification will happen here
  }
  __declspec(property(get = get_age, put = put_age)) int Age;
};

Okay, so once you’ve got everything in place, you can use it as follows:

Person p;
p.Age = 33;
p.Age++;

Under the covers, your assignment/increment/whatever mutating calls will be proxied over to put_age, which does three things:

  1. Checks that the value has in fact been changed, and kicks us out otherwise.
  2. Assigns the value (duh!).
  3. Notifies whoever’s listening that the value has changed.

1. and 3. are required for sensible change notification, though 3. is not yet shown since we haven’t implemented it yet.

Oh, and just in case you’re wondering about encapsulation, I want to note two things:

  • There is no way to hide the getter/setter functions. If you make them private, your program will not compile, because they are actually used when reading/writing values.
  • You can make the field private. A small consolation prize, I guess.

Change Notification

In .NET, notifications are done using events, which are first-class implementations of the Observer pattern built right into the language. In C++, events are typically a library solution implemented in libraries such as Boost.Signals2 (for more info on Boost see my course). In C++, events are typically implemented using the signal and slot paradigm, which we are going to leverage.

Let’s define a uniform interface… ugh, I mean class, for sending change notifications (C++ doesn’t have interfaces as such, either):

template <typename T> class INotifyPropertyChanged
{
public:
  signal<void(T&, string)> PropertyChanged;
};

The name here is stolen from the equivalent .NET name. Now, what this class has is a signal that we fire when a particular property is changed. The first argument passes a reference to the object, the second contains the name of the property that was changed.

Having written this, we can now inherit this type in Person:

class Person : public INotifyPropertyChanged<Person>
{
public:
  int age;
  int get_age() const { return age; }
  void put_age(int value)
  {
    if (age == value) return;
    age = value;
    PropertyChanged(*this, "Age"); // <-- we notify here!
  }
  __declspec(property(get = get_age, put = put_age)) int Age;
};

So, now that we’ve implemented this, any change in the value of age will be sent as a notification to the subscribers. How do we get notifications? Well, this is rather easy:

Person p;
p.PropertyChanged.connect([](Person& p, string name){
  if (name == "Age")
    cout << "Person's age changed to " << p.Age << endl;
});
p.Age = 44;
p.Age++;

And the output is:

Person's age changed to 44
Person's age changed to 45

Conclusion

This is an illustration of some of the hoops you have to jump to get things to work the way you want. I actually use __declspec(property) in many places in my programs and while some people might see it as non-standard or whatnot, I really do not care. ■

Written by Dmitri Nesteruk

September 12th, 2014 at 9:36 am

Posted in Cpp