Jump to content

  • Log In with Google      Sign In   
  • Create Account

Like
25Likes
Dislike

Using Varadic Templates for a Signals and Slots Implementation in C++

By Paul Cook (Aardvajk) | Published Aug 17 2014 06:07 AM in General Programming
Peer Reviewed by (Migi0027, Dave Hunt, Cygon)

C++ signals and slots varadic templates

Abstract


Connecting object instances to each other in a type-safe manner is a well-solved problem in C++ and many good implementations of signals and slots systems exist. However, prior to the new varadic templates introduced in C++0x, accomplishing this has traditionally been complex and required some awkward repetition of code and limitations.

Varadic templates allow for this system to be implemented in a far more elegant and concise manner and a signals/slots system is a good example of how the power of varadic templates can be used to simplify generic systems that were previously difficult to express.

This system is limited to connecting non-static class methods between object instances in order to keep the article focused and to meet the requirements for which the code was originally designed. Connecting signals to static or non-member functions is an extension not discussed here. Events in this system also have no return type as returning values from a signal potentially connected to many slots is a non-trivial problem conceptually and would distract from the central concept here.

The final product of this implementation is a single, fairly short header file that can be dropped into any C++0x project.

Because I work with QtCreator, I chose to name this system as Events and Delegates rather than signals and slots as QtCreator treats certain words as reserved due to Qt's own signals/slots system. This is purely cosmetic and irrelevant to the article.

A detailed discussion of varadic templates is beyond the scope of this article and will be discussed purely in relation to this specific example.

Code is tested with GCC 4.7.2 but should be valid for any C++0x compliant compiler implementing varadic templates

Overview


The system provides two main classes - Event which represents a signal that can be sent from an object, and Delegate which is used to connect external signals to internal member functions.

Representing the connections as members allows for modelling of the connections via the object lifetimes so that auto-disconnecting of connections when objects are destroyed can be expressed implicitly with nothing required from the programmer.

Event


Lets start by looking at the Event class as an introduction to the varadic template syntax. We need a class that is templated on any number of parameters of any types to represent a generic signal sent by an object.

template<class... Args> class Event
{
public:
    void operator()(Args... args){ }
}

This is the basics of varadic templates. The class... Args is expanded to a comma-separated list of the types provided when the template is instatiated. For example, under the hood, you can think of the compiler doing something like this:

Event<int, float, const &std::string> event;

template<int, float, const &std::string> Event
{
public:
    void operator()(int, float, const std::string&){ }
};

For simplicity, lets imagine we have a normal function taking these parameters, so we can look at how we call it from within the body of the operator():

void f(int i, float f, const std::string &s)
{
}

template<class... Args> class Event
{
public:
    void operator()(Args... args){ f(args...); }
}

Event<int, float, const std::string&> event;
event(10, 23.12f, "hello");

The args... syntax will be expanded in this case to 10, 23.12f, "hello", which the normal rules of function lookup will resolve to the dummy f method defined above. We could define multiple versions of f taking different parameters and the resolution would then be based on the specific parameters that Event is templated upon, as expected.

Note that the names Args and args are arbitrary like a normal template name. The ellipses is the actual new syntax introduced in C++0x.

So we now have a class to represent an event that can be templated on any combination and number of parameters and we can see how to translate that into a function call to a function with the appropriate signature.

Delegate


The Event class needs to store a list of subscribers to it so that the operator() can be replaced by a method that walks this list and calls the appropriate member of each subscriber. This is where things become slightly more complicated because the subscriber, a Delegate, needs to be templated both on its argument list and also the type of the subscriber object itself. Core to the whole concept of generic signals and slots is that the signal does not need to know the types of the subscriber objects directly, which is what makes the system so flexible.

So we need to use inheritance as a way to abstract out the subscriber type so that the Event class can deal with a representation of the subscriber templated purely on the argument list.

template<class... Args> class AbstractDelegate
{
public:
    virtual ~AbstractDelegate(){ }

    virtual void call(Args... args) = 0;
};

template<class T, class... Args> ConcreteDelegate : public AbstractDelegate
{
public:
    virtual void call(Args... args){ (t->*f)(args...); }

    T *t;
    void(T::*f)(Args...);
};

Note that the varadic template usage is just being combined with the existing pointer-to-member syntax here and nothing new in terms of varadic templates is introduced. Again we are simply using Args... to replace the type list, and args... to replace the parameter list, just like in the simpler Event example above.

So now we can expand Event to maintain a list of AbstractDelegate pointers which will be populated by ConcreteDelegates and the system can translate a call from Event using only the argument list to call to a method of a specific type:

template<class... Args> class Event
{
public:
    void operator()(Args... args){ for(auto i: v) i->call(args...); }

private:
    std::vector<AbstractDelegate<Args...>*> v;
}

Note the use of the for-each loop also introduced in C++0x. This is purely for brevity and not important to the article. If it is unfamiliar, it is just a concise way to express looping across a container that supports begin() and end() iterators.

Connections in this system need to be two-way in that Delegate also needs to track which Events it is connected to. This is so when the Event is destroyed, the Delegate can disconnect itself automatically. Thankfully we can use Event as-is inside AbstractDelegate since it is only templated on the argument list:

template<class... Args> class AbstractDelegate
{
public:
    virtual void call(Args... args) = 0;

private:
    std::vector<Event<Args...>*> v;
};

The final class that we need to look at is motivated by the fact that creating a separate object inside each recieving class to represent each slot is tedious and repetitive, since the recieving object requires both a member function to be called in response to the signal, then an object to represent the connection. The system instead provides a single Delegate object that can represent any number of connections of events to member functions, so a recieving object need only contain a single Delegate instance.

We need therefore to have a way to treat all AbstractDelegates as the same, regardless of their argument lists, so once again we use inheritance to accomplish this:

class BaseDelegate
{
public:
    virtual ~BaseDelegate(){ }
};

template<class... Args> class AbstractDelegate : public BaseDelegate
{
public:
    virtual void call(Args... args) = 0;
};

We can now store a list of BaseDelegates inside the Delegate class that can represent any AbstractDelegate, regardless of its parameter list. We can also provide a connect() method on Delegate to add a new connection, which has the added advantage that the template arguments can then be deduced by the compiler at the point of call, saving us from having to use any specific template types when we actually use this:

class Delegate
{
public:
    template<class T, class... Args> void connect(T *t, void(T::*f)(Args...), Event<Args...> &s){ }
    
private:
    std::vector<BaseDelegate*> v;
};

For example:

class A
{
public:
    Event<int, float> event;
};

class B
{
public:
    B(A *a){ delegate.connect(this, &B::member, a->event); }

private:
    Delegate delegate;

    void member(int i, float f){ }
};

All that really remains now is some boiler-plate code to connect Events and Delegates and to auto-disconnect them when either side is destroyed. A detailed discussion of this is not really related to varadic templates and just requires some familiarity with using the standard library methods.

Fundamentally, ConcreteDelegate should only be constructable with a pointer to a reciever, a member function and an Event. Connecting an Event to an AbstractDelegate should also add the Event to the AbstractDelegate's list of Events.

When an Event goes out of scope, it needs to signal all its Delegates to remove it, and when a Delegate is destroyed, it needs to tell all the Events it is listening to to remove it. Explcit disconnection is not implemented here but could be trivially added if required.

An implementation of this full system just uses the usual std::vector and std::remove methods of the standard library.

Note in this implementation, all classes are defined to be non-copyable as it is hard to come up with a sensible strategy for copying behaviour of both Events and Delegates and for the purposes this is designed for, it is not necessary.

#include <vector>
#include <algorithm>

template<class... Args> class Event;

class BaseDelegate
{
public:
    virtual ~BaseDelegate(){ }
};

template<class... Args> class AbstractDelegate : public BaseDelegate
{
protected:
    virtual ~AbstractDelegate();

    friend class Event<Args...>;

    void add(Event<Args...> *s){ v.push_back(s); }
    void remove(Event<Args...> *s){ v.erase(std::remove(v.begin(), v.end(), s), v.end()); }

    virtual void call(Args... args) = 0;

    std::vector<Event<Args...>*> v;
};

template<class T, class... Args> class ConcreteDelegate : public AbstractDelegate<Args...>
{
public:
    ConcreteDelegate(T *t, void(T::*f)(Args...), Event<Args...> &s);

private:
    ConcreteDelegate(const ConcreteDelegate&);
    void operator=(const ConcreteDelegate&);

    friend class Event<Args...>;

    virtual void call(Args... args){ (t->*f)(args...); }

    T *t;
    void(T::*f)(Args...);
};

template<class... Args> class Event
{
public:
    Event(){ }
    ~Event(){ for(auto i: v) i->remove(this); }

    void connect(AbstractDelegate<Args...> &s){ v.push_back(&s); s.add(this); }
    void disconnect(AbstractDelegate<Args...> &s){ v.erase(std::remove(v.begin(), v.end(), &s), v.end()); }

    void operator()(Args... args){ for(auto i: v) i->call(args...); }

private:
    Event(const Event&);
    void operator=(const Event&);

    std::vector<AbstractDelegate<Args...>*> v;
};

template<class... Args> AbstractDelegate<Args...>::~AbstractDelegate()
{
    for(auto i : v) i->disconnect(*this);
}

template<class T, class... Args> ConcreteDelegate<T, Args...>::ConcreteDelegate(T *t, void(T::*f)(Args...), Event<Args...> &s) : t(t), f(f)
{
    s.connect(*this);
}

class Delegate
{
public:
    Delegate(){ }
    ~Delegate(){ for(auto i: v) delete i; }

    template<class T, class... Args> void connect(T *t, void(T::*f)(Args...), Event<Args...> &s){ v.push_back(new ConcreteDelegate<T, Args...>(t, f, s)); }

private:
    Delegate(const Delegate&);
    void operator=(const Delegate&);

    std::vector<BaseDelegate*> v;
};

Examples


Let's look at some concrete examples of this in relation to a game project. Assume we have an Application class that is called when a Windows message is processed. We want to be able to have game objects subscribe to certain events, such as key down, application activated etc.

So we can create an AppEvents class to pass around to initialization code to represent these and trigger these events within the Application message handler:

class AppEvents
{
    Event<bool> activated;
    Event<int> keyDown;
};

class Application
{
public:
    LRESULT wndProc(UINT msg, WPARAM wParam, LPARAM lParam);

private:
    AppEvents events;
};

LRESULT Application::wndProc(UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        case WM_ACTIVATE: events.activated(static_cast<bool>(wParam)); return 0;
        case WM_KEYDOWN : if(!(lParam & 0x40000000)) events.keyDown(wParam); return 0;

        case WM_LBUTTONDOWN: events.mouseDown(Vec2(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)), VK_LBUTTON); return 0;
    }

    return DefWindowProc(hw, msg, wParam, lParam);
}

Now when we create a game object, we just make the AppEvents instance available to its constructor:

class Player : public GameItem
{
public:
    Player(AppEvents &events, const Vec3 &pos);

private:
    void appActivated(bool state){ /* ... */ }
    void keyDown(int key){ /* ... */ }

    Delegate delegate;
};

Player::Player(AppEvents &events, const Vec3 &pos) : pos(pos)
{
    delegate.connect(this, &Player::appActivated, events.activated);
    delegate.connect(this, &Player::keyDown, events.keyDown);
}

Player *Application::createPlayer(const Vec3 &pos)
{
    return new Player(events, pos);
}

Another area this is useful is in dealing with dangling pointers to resources that have been removed elsewhere. For example, if we have a Body class that wraps a rigid body in a physics system, and a Physics class that is responsible for adding and removing bodies to the world, we may end up with references to a body that need to be nullified when the body is removed.

It can be useful then to give the Body a destroyed(Body*) event that is called from its destructor.

class Body
{
public:
    ~Body(){ destroyed(this); }
    
    Event<Body*> destroyed;
}

The physics system can then connect to this event when it creates the body and use it to remove the body from the physics world when it is destroyed. This saves having each body storing a reference to the Physics instance and manually calling it from its destructor and means the body removal no longer needs to be part of the public interface of the Physics class.

Body *Physics::createBody()
{
    pRigidBody *b = world->createBody();

    Body *body = new Body();
    body->setRigidBody(b);

    delegate.connect(this, &Physics::bodyDestroyed, body->destroyed);
    
    return body;
}

void Physics::bodyDestroyed(Body *body)
{
    pRigidBody *b = body->getRigidBody();
    world->removeBody(b);
}

In addition, any other class that holds a reference to the body that does not actually own it can choose to subscribe to the destroyed(Body*) event to nullify its own reference:

class Something
{
public:
    Something(Body *ref) : ref(ref) { delegate.connect(this, &Something::refLost, ref->destroyed); }

private:
    void refLost(Body *b){ ref = 0; }

    Body *ref;
};

Now anywhere else in the code, you can just delete the Body instance or maintain it with a smart pointer, and it will be both removed from the Physics world and also any other non-owning references to it get the opportunity to be updated, without the overhead of having to call methods on every possible object that might own such a reference.

Conclusion


Varadic templates are a powerful addition to C++ that make code that was previously verbose and limited far more elegant and flexible. This is only one example of how they allow for systems that have both type-safety and generic features implemented at compile time. The days of dreading the ellipse operator are over, since we can now use it in a type-safe manner and the possiblities are endless.



About the Author(s)


Paul Cook is a professional programmer by day and a hobbyist game developer by night. He has been grappling with C and C++ for around 20 years and is currently trying to stay up-to-date with the recent features added in C++0x

License


GDOL (Gamedev.net Open License)




Comments

I like it.

 

Great work, it'd be awesome if you'd make more of these, keep it up!

Loved this. I was recently trying to come up with something similar to this, but was hitting a few snags. I think your article is going to really help.

You can disable QtCreator's use of 'slot', 'signal', and 'emit' as keywords by adding the following line to your .pro qmake file:

CONFIG += no_keywords

(I think that's the correct command)

 

If you want to disable those keywords, but still need to use those features, you can use Q_SLOT, Q_SIGNAL, and Q_EMIT instead, so no conflicts occur.

You can disable QtCreator's use of 'slot', 'signal', and 'emit' as keywords by adding the following line to your .pro qmake file:

CONFIG += no_keywords

(I think that's the correct command)

 

If you want to disable those keywords, but still need to use those features, you can use Q_SLOT, Q_SIGNAL, and Q_EMIT instead, so no conflicts occur.

 

Ah, thanks. I didn't think that affected QtCreator syntax hilighting but didn't actually try it.

Thanks for the positive responses, appreciated. Hope I was right to gloss over the actual implementation of all the boiler-plate stuff. I felt it was distracting from the focus of the article.

This is interesting. I made a similar system for my events in my engine, although I used the Callback/Dispatcher terminology and I preferred having a CallbackTarget base class that all classes with handlers have to implement instead of having to store the target as a member variable. (ie: your Delegate class.) This is because I need to be able to connect an event to a handler without the handler necessarily having to be the object that does the connection, ie:

ok_button.ClickEvent.AddCallback(&some_other_object, &Klass::OkButton_Click);

This is kind of a corner case though. Most of the time an object will be connecting an event to itself, not to another object, and your handler will be a private method anyway. But if you wanted to do that, you would have to provide a GetDelegate() method for all objects that have one such Delegate instance, hence why I personally prefer a base class. Of course, my CallbackTarget destroys all callbacks bound to it on destruction, just like your Delegate class.

 

Another thing, and much less of a corner case this time. What happens if an event handler causes the Event object to be destroyed or adds/remove a callback? Because if the Event is destroyed right in the middle of the for loop that invoked the handler, this will break the iterator and you will get a runtime error if you do a iterator++. Both of those situations are common in UI programming, and that has slapped me in the face before. For example, a Quit button might destroy the window that houses the Event that called its handler.

 

In case you'd like to know, my fix for the destroy problem was to create a flag that is raised by the destructor of the callback dispatcher. When it is raised, the loop exits immediately and skips the remaining event handlers. However, in order to survive destruction, that flag has to be static, which isn't awesome if you're doing multi-threaded applications and in that case requires locking. (So I don't pretend my solution to be perfect.) My solution for the problem of adding and removing handlers was to have another flag that causes the for-loop to reset itself, and then keep calling the callbacks which have not been called already.

Very nice!

 

Your use of the term 'delegate' is a little bit unusual (since they're not representing a method directly, but just the connection between an event and its subscriber). Maybe 'subscription' or 'forwarder' would also be good terms for this.

 

It would also be interesting to have a performance comparison. There's the by now well-known FastDelegate library (with limited portability) and the Impossibly Fast Delegate library which should be portable. Some community members have created signal/slot systems based on these, so it would be pretty interesting to see how this one holds up against that.

 

@Bearhugger: that is an interesting problem :)

 

I'd imagine that even having a flag in the class that is set by the destructor would still rely on the memory not having been reused by the time the running iteration gets to check it. The only safe way I can think of would be to register some local variables in an instance-wide notification list...

 

...but then destruction could still happen while the event is executing the call into the subscriber. And the subscriber is likely to access the event publisher in the callback. Maybe it's best to require that subscribers don't destroy their event publishers from within the callback :P

class Event {

  public: ~Event() {
    std::lock_guard<std::mutex> iterationLockScope(this->iterationLock);
    for(std::size_t index = 0; index < this->activeIterations.size(); ++index) {
      *this->activeIterations[index] = true;
    }
  }

  public: void Fire() {
    std::vector<Delegate> copiedSubscribers
    bool destroyed = false;

    // Still a race condition up to this point... :/

    {
      std::lock_guard<std::mutex> iterationLockScope(this->iterationLock);
      ScopeGuard activeIterationScope = MakeGuard(
        this->activeIterations, std::vector<bool *>::erase, &destroyed
      );
      copiedSubscribers.swap(std::vector<Delegate>(this->subscribers));
    }

    for(std::size_t index = 0; index < copiedSubscribers.size(); ++index) {
      if(destroyed) {
        break;
      }
      copiedSubscribers[index]();
    }
  }

  private: std::vector<Delegate> subscribers;
  private: std::vector<bool *> activeIterations;
  private: std::mutex iterationLock;

};

Thanks guys.

 

Bearhugger, you are quite correct, I missed that. Bad things would indeed happen if an event was destroyed during an event handler. I'll have to have a think about the best way to handle that, thanks.

 

Cygon, thanks for the comment. I've seen the Fast Delegate library and don't doubt it would outperform mine, so not sure it is even worth running such a test. This was intended more as an exploration of how varadic templates can simplify traditionally complex code and if performance is an issue, I'd certainly never recommend my rather naive implementation. Performance was never really considered here.

Great article! I created a similar code some months ago (using the "Callback" terminology), but a feature is still missing in my code (and I guess in yours too) ... what about "Queued Connection"? I'd like to create an "event loop"-like calling method. I can pack parameters and store them in a list of tuple but how can I use use later?? I have not been able to find a working solution yet

Good article, could have really used this a few years ago :D

 

I think I found one point of improvement, actually:

 

Args... args

 

You might consider using rvalue-references instead:

Args&&... args

Now I'm not entirely sure if this is the better thing here, but I've been using it myself all over the place with variadic templates (one implementation being my own signals/slots). I think the reason being that it allows perfect forwarding of the arguments, but I'm not totally convinced here... you might want to look more into it yourself.

becrux - interesting problem, certainly worth some investigation.

 

Juliean - Yes, I wondered about that after I posted the article. I realised I needed to look into that more. I found some weird issues when trying to do some other forwarding that don't seem to affect the system discussed here but something that needs some further investigation, thanks.

"C++0x"... What is this? 1998? C++14 has just been approved! :-P

 

But good article, thanks.

 

I've started using variadic template to implement a state machine in which the appropriate event handler method is selected based on the type of the event (no more "switch (event->id)"). Your signal&slots would complement that nicely, to forward events to a chosen state machine...

Heh, true. I've normally settled at about ten years behind the curve in my programming career, so your dating seems about right :)

I don't think AbstractDelegate's add and remove methods should be virtual, because add() is called (via Event::connect()) from ConcreteDelegate's constructor.

About the problem Bearhugger raised, ie deleting an Event or disconnecting/deleting a delegate during a handler. I don't think deleting an Event during a handler is something that most people would want to do, so you could probably get away with forbidding this in the documentation, although hopefully someone might find a way to code around that - perhaps wrap the Event in a shared_ptr?

 

I think there's an easy way to deal with delegates disconnecting themselves or each other while being called. Instead of removing a disconnected delegate from its Event's vector straight away, replace it with a nullptr, which can easily be skipped while iterating through the list and removed afterwards.

I don't think AbstractDelegate's add and remove methods should be virtual, because add() is called (via Event::connect()) from ConcreteDelegate's constructor.

 

Hmm, you're right. Will check that and edit the article, thanks. Not sure how that ended up in there.

 

[Tested and article fixed, thanks realh]

About the problem Bearhugger raised, ie deleting an Event or disconnecting/deleting a delegate during a handler. I don't think deleting an Event during a handler is something that most people would want to do, so you could probably get away with forbidding this in the documentation, although hopefully someone might find a way to code around that - perhaps wrap the Event in a shared_ptr?

 

I think there's an easy way to deal with delegates disconnecting themselves or each other while being called. Instead of removing a disconnected delegate from its Event's vector straight away, replace it with a nullptr, which can easily be skipped while iterating through the list and removed afterwards.

 

Yes, I think the reason I forgot to consider it is that it is outside of the use cases I designed this for. This was originally written to be used for one of my projects rather than as an abstract exercise and a quick snippet journal post got turned, by request, into an article. I've not, perhaps, refined this sufficiently to be presented as an article.

 

But the real focus of the article is meant to be on how varadic templates can simplify previously ugly and inelegant code so I shall play the old "Improvements are left as an exercise for the reader" card here smile.png

 

But the real focus of the article is meant to be on how varadic templates can simplify previously ugly and inelegant code so I shall play the old "Improvements are left as an exercise for the reader" card here smile.png

In that respect it was a good job I think. It appeared at just the right time for me. I needed some sort of event-listener setup but I had no experience of variadic templates so wouldn't have thought of this solution. Reimplementing it with a few customisations of my own helped me understand it better.

I have a question regarding the implementation of AbstractDelegate.

AbstractDelegate has a member called v which is a vector of events but to me it seems the vector would always be of size 1 (or 0) because for each new connection the Delegate class creates a new instance of ConcreteDelegate. So is there actually a reason to store a vector of events in AbstractDelegate or am I missing something?

 

Thanks.

 

BTW, Great article.

I have a question regarding the implementation of AbstractDelegate.

AbstractDelegate has a member called v which is a vector of events but to me it seems the vector would always be of size 1 (or 0) because for each new connection the Delegate class creates a new instance of ConcreteDelegate. So is there actually a reason to store a vector of events in AbstractDelegate or am I missing something?

 

Thanks.

 

BTW, Great article.

 

Hmm, I think you are correct. Think I was originally planning to have the ConcreteDelegate used directly by the API and when I wrapped it up in the Delegate class, I forgot that. Good catch. It does now seem that a delegate will be connected to at most one event.

 

Cheers.


Note: Please offer only positive, constructive comments - we are looking to promote a positive atmosphere where collaboration is valued above all else.




PARTNERS