Fake binary trees
... rants, ramblings and occasional good idea ...

C#-like events in C++

Since most of my daily work is done in C++,  every now and then I miss some niceties which are available in C#. One such example are .NET events, which are really an elegant solution for implementing observer design pattern.

Idiomatic C++ implementations of observer pattern are based on defining abstract interfaces, which have to be implemented by subject and observer classes. Sometimes it would be nicer to bind to an event just based on event/method signature instead of building all the needed scaffolding.

Here is a quick take at this problem. This is by no means a complete solution, but can be a basis for more elaborate one.

Events are defined and fired like this:

class Subject
{
public:
    event<int> ValueChanged; // same template is used, regardless of number of arguments
    event<int, const char*> ValueAndNameChanged;  // no need for having event1<A> and event2<A, B>

    void SetValue(int x)
    {
        // .. do something
        ValueChanged(x); // then fire ValueChanged event
    }

    void SetValueAndName(int x, const string& name)
    {
        // do something...
        ValueAndNameChanged(x, name.c_str()); // fire ValueAndNameChanged event
    }
};

Clients consume events like this:

// implementation of observer object
class Observer
{
public:
    void OnValueChanged(int i)
    {
        printf(" - non-static handler for SetValue called, new value = %ld\n", i);
    }

    static void OnValueChangedStatic(int i)
    {
        printf(" - static handler for SetValue called, new value = %ld\n", i);
    }
};

// global functions work, too:
void OnValueChangedGlobal(int i)
{
    printf(" - global handler for SetValue called, new value = %ld\n", i);
}

// test
int _tmain(int argc, _TCHAR* argv[])
{
    Subject subject;
    Observer observer;

    subject.ValueChanged += Observer::OnValueChangedStatic; // static member method as handler
    subject.ValueChanged += OnValueChangedGlobal; // global function as handler
    subject.ValueChanged += event_handler(&observer, &Observer::OnValueChanged); // non-static member

    subject.SetValue(3); // this will fire the event

    // detach from event. This is not necessary when lifetime of observer is shorter than lifetime
    // of the subject, but if subject outlives observer, handler must be unregistered
    subject.ValueChanged -= Observer::OnValueChangedStatic;
    subject.ValueChanged -= OnValueChangedGlobal;
    subject.ValueChanged -= event_handler(&observer, &Observer::OnValueChanged);

    return 0;
}

Output of this snippet is:

d:\dev\events>events.exe
  - static handler for SetValue called, new value = 3
  - global handler for SetValue called, new value = 3
  - non-static handler for SetValue called, new value = 3

It is possible to use static and non-static members as handlers, as well as global functions. Note, however, that there is one difference compared to event behavior in .NET: if you register same handler twice, it will be called only once. This can easily be changed in the code, but it seems more logical to me.            

Attached archive contains a Visual Studio solution with implementation of event class (events.h) and some additional examples. 

events.zip (5.14 kb)

Posted at 16:12 on April 29, 2008
Categories: C++   E-mail | del.icio.us | Permalink | Comments (2) | Post RSSRSS comment feed

Comments

Stoyan wrote on September 25. 2008 at 22:25:

The more elaborate (native C++) solution has already been defined in Boost.Signals

Cheers

zdeslav wrote on September 26. 2008 at 11:16:

Yes, I know, I did it mostly for fun, but I also prefer syntax with -=/+= operators, and I wanted to avoid different class names based on number of parameters (signal1/signal2/signal3) so I have event type, regardless of number of parameters. Additionally, implementation could be more elegant, but I didn't have more time to finish it off.

Of course, for a real project, I would recommend Boost stuff.

I am not sure that I understood 'native C++' part. If you meant standard C++ (as in non-managed), this solution is also standard C++.

Comments are closed