C++ Events and Delegates - by Geoff Evans

Hi folks,

One of the coolest things about working for a company like Insomniac Games is being able to open source technology that our tools team creates.  Nocturnal is our open source project, and one of the most useful things included with the ToolsFramework project is its c++ event and delegate system.

Any programmer that has used Microsoft’s .NET framework will tell you that the events and delegates implementation in .NET is incredibly useful. It pervades the entire Windows Forms framework. UI element callbacks, plug-in APIs, and application automation are all made easier by this design pattern. Unfortunately jumping into the .NET Framework doesn’t go without significant trade-offs.

When Insomniac ported its tools from .NET to wxWidgets we definitely wanted something as slick as .NET events and delegates. This is what we came up with, and we have been iterating on it for several years now. Most of our key libraries and applications lean on it heavily.

This implementation supports a lot of tricky cases that aren’t easily handled by function (or member function) pointers alone. Delegates can be added and removed within delegates of the same event and objects that own events can be deleted from within delegates of the same event.

To get started, just typedef the Signature template with your return and parameter types. Then use the Event class (a member class of Signature) to make a multi-cast event, or Delegate to make a single-cast event.

EventDemo.zip

Geoff Evans
Insomniac Games

11 Responses to “C++ Events and Delegates”

  1. Aaron Walker says:

    What motivated your company’s decision to move from .NET to wxWidgets?

  2. Michael says:

    I’m also curious about the .NET / wxWidgets decision.

  3. gorlak says:

    This is an important question, so I wrote another post about it here.

  4. snk_kid says:

    Is there any particular reason you didn’t use an existing signals & slots library such as Boost.Signals? (boost has recently added a thread-safe implementation too). Using a named method to “fire an event” is bit ugly. It is a `callable entity` it should overload the functional call operator, be consistent with generic programming techniques.

  5. gorlak says:

    I didn’t know about the boost::signals stuff, thanks for pointing that out. Seems pretty similar in terms of feature set. We use this event/delegate code on platforms we don’t want to port boost to (like PS3). Is it possible to break out parts of boost, or is it pretty much a monolithic lib?

    Calling a named method to raise the event is very clear in calling code IMO. What are some resources for generic programming techniques? What do you mean by ‘capable entity’? I personally have never bought into the implementation styles of stl and boost. I am sure I don’t know the trade-offs however…

  6. snk_kid says:

    Sorry about the first post I was a bit of an ass there, boost isn’t the only C++ library for signals & slots library out there by the way.

    Are you sure you’ll be able to use such a thing on the console dev? I usually work in tools so It’s alright for me to use such components but on console dev we are very strict and careful as to what we use because as I’m sure you very well know we are dealing with a number of constraints & variables that can really hurt performance if we are not careful and get sloppy.

    Most/all components in boost are independent of each other and self-contained however they try to target many platforms and compilers so they have a relatively complicated header files preprocessor configuration to deal with the varying compiler C++ ISO standard conformance and have workarounds (if they exist).

    Pretty much the majority of boost components are generated from macro and template metap-rogramming (but some components do generate static/dynamic link libraries), most likely using Boost.Preprocessor (a very useful preprocessor library and works with pure C compilers) and boost::mpl (template meta-programming library).

    I don’t know but (ignoring all the potential issues with compilers for consoles and hardware constraints) it might be a bit daunting to try and make a port but you can easily talk to the authors on the dev mailing lists. If there is something you’re not sure about.

    They do have an installer to select the various types of libraries (static/dynamic libraries, statically/dynamically linking to CRT and so on) to download and install, here (click me).

    One thing to note about boost their components have had a major influence on the next C++ ISO standard revision (C++0x) such as smart pointers and “polymorphic function object wrappers” (boost/std::tr1::function) and not just libraries even the language itself. This is no surprise since some of the members are part of the C++ ISO Standard committee.

    Anyways back to the last question, to me it’s less clear than “my_signal(params);”, and this is the same syntax used for .NET delegates but I do see some advantages of having a named method.

    I actually said callable entity not capable lol, `callable entity` is C++ terminology for anything you can call/invoke with “()” syntax. It could be free-function, a bound pointer to member function, functional object. This is a major hint about generic programming and what I was talking about and the advantage of providing a functional call operator overload (you could have both methods).

    I don’t know if you’re aware of this but you can use templates to pass in `callable entities` in your functions and not be limited to just free-functions or pointer to member functions, it’s very polymorphic like but still being efficient, an example:

    template
    inline void for_each_something_do(InputIterator first, InputIterator last, UnaryFunction fun)
    {
    for(; first != last; ++first)
    fun(*first);
    }

    Now you can pass any `callable entity`, even with your signals if you provide an overload of the functional call operator:

    inline void print_int(int i) { std::cout << i << ‘\n’; }

    struct int_printer {
    void operator()(int i) const { std::cout << i << ‘\n’; }
    };

    for_each_something_do(my_ints.begin(), my_ints.end(), print_int);
    for_each_something_do(my_ints.begin(), my_ints.end(), int_printer());

    The C++ generic algorithms (and C++ standard library) pretty much works (and is used) like this, very functional by nature.

    Now you can use functional objects you can take the idea further and emulate lexical closures in C++. They can `capture the environment` and your "functions" can carry state. Does your framework all working with functional objects like boost signals?

    Note that C++0x officially introduces lambda expressions and lexical closures which equate to the compiler generating structs with functional call operators, all on the stack.

    I notice that your implementation is already using the standard library vector so I didn’t understand the comment about "never bought into the implementation styles".

    Can I just give a little advice, please use (nested) type aliases for iterator types! instead of std::vector::iterator/const_iterator all over the place, you can even do it in function scope level, as they say apply the DRY principle, nested type aliases is also a very big part of generic programming actually.

    I’m sorry if I come off as an asshole, I really don’t mean to.

  7. snk_kid says:

    Hmmm, some of my code got lost in a webpage singularity, it should have been:


    template
    inline void for_each_something_do(InputIterator first, InputIterator last, UnaryFunction fun)
    {
    for(; first != last; ++first)
    fun(*first);
    }

  8. Geoff Evans says:

    > Are you sure you’ll be able to use such a thing on the console dev?

    We do maintain a scratch heap on the devkit runtime to allow using standard containers and small allocations for doing runtime-support for tools tasks. You do have to be careful that general heap usage doesn’t creep into your final on-disc code.

    > I notice that your implementation is already using the standard library vector so I didn’t understand the comment about “never bought into the implementation styles”.

    I am talking about the specifics of the implementation of stl and boost libs, not their usage. Using a std container and really digging into the implementation details are separate things IMO.

    That callable entity stuff is interesting, but for me it goes just a tad beyond ‘Keep it simple stupid’, if you get my drift. Perhaps its just me being old-fashioned though :) We have a hard enough time with our hard core engine guys with straightforward C++ (classes, function, constructors, virtual APIs, and template classes), much less getting really intricate with stl with for_each, lambdas, etc… I think there is something to be said about keeping C++ reigned in… Though I should shut up about that because I have written some insane template code over the years that are arguably as obtuse as these callable entities.

  9. snk_kid says:

    I don’t think it’s beyond KISSing, it’s very common C++, I’m probably just making it sound much more complicated than it actually is. It’s just static polymorphism of templates, just like when you have a template function and in it’s definition you use the operator + or some named method, it could be any type as long as the real type used supports the operation(s) the code will compile.

    This is no different and the term callable entity isn’t commonly used to be honest, just a technical term to refer to something that you can invoke with the “(params)” syntax without refer specifically to free-functions.

    Code like this seems KISSable:

    bool some_custom_predicate(int lhs, int rhs) { … }
    //….

    std::sort(my_ints.begin(), my_ints.end(), some_custom_predicate);

    or

    struct some_custom_predicate_with_state {
    // some state
    bool operator()(int lhs, int rhs) const { … }
    };
    //….
    std::sort(my_ints.begin(), my_ints.end(), some_custom_predicate_with_state());

    std::sort has an overload for all types that support the less than operator so you don’t need to use custom predicates at all. This same technique works for class templates (like std::set/map) as well but you need to be able to pass the address of a free-function in the class’s constructor of-course.

    Anyways I’m not trying to convince you because I understand what it’s like, it’s the same where I work, every (games) programmer is on a different level of C++, some guys are from a C background and they usually see it all as overly complicated and potentially inefficient (and can be quite easily when not used properly/carefully) and actually I think I agree with the C guys to a certain extent! it doesn’t help matters further with multiple compiler vendors and multiple implementations of the standard library with varying efficiencies and build times, you have no control.

    Also these guys are all domain experts in some particular area and it takes many, many years to become an expert so knowing C++ very, very in itself isn’t that important.

  10. Will Miller says:

    I’ve been using your event code for a while, and it totally rocks. I was disappointed, however, when I was unable to compile it with GCC 4.0 on the Mac. Are there any compatibility issues that you are aware of?

  11. Geoff Evans says:

    >I’ve been using your event code for a while, and it totally rocks. I was disappointed, however, when I was unable to compile it with GCC 4.0 on the Mac. Are there any compatibility issues that you are aware of?

    Hi Will!

    Yeah, its a quick fix, you just need to add the typename keyword in front of all the iterators:

    typename std::vector::const_iterator itr = m_Delegates.begin();

Leave a Reply