Wednesday, April 23, 2014

Code Dump: C++ delegate

When I started to learn to program, a lot of stuff seemed like magic. If you're a programmer, you probably know the feeling. It's just like when you are told how to do something, rather than getting an explanation of how it works under the hood.

For me, that happened all the time using C# delegates. See, I really liked C# delegates. I thought they were the shit (and what all the pros used, don't ya know). Turns out they aren't good in every situation (most situations call for a more simple and sufficient event pattern) but regardless, they can be very useful at certain times.



When building UI elements, they can be incredibly useful, especially if they are made to be robust and can adapt to a lot of situations. Now, as a predominately C++ programmer, I've found myself needing to use delegates on more than one occasion. The only problem is that C++ doesn't natively support delegates. I'm cool with that -- its a low level language, and as Bjarne Stroustrup is known to say, C++ is general, so that libraries can be build to suppose specific needs. Not all disciplines need to use delegates, so it doesn't exactly need to be a feature of the language (lots of people will disagree with that, and that's okay).

In any case, if you want to use a delegate pattern in C++, here's a code dump that can help you get started. The code is marked up with comments to add a bit of clarity of what's going on. Be warned, you'll need to know a good deal about templates to understand what's going on.

Note: I copy and pasted this from a project of mine, and simplified it a bit for the sake of this blog post. The concept works, although I haven't actually built this particular snippet of code, so you may find warnings or errors in it.

//  This is just a templated interface. The actual delegate will have a pointer to this type, that the actual callback objects will be stored in.
template<typename TReturn, typename TParam> class ICallback
{
    virtual TReturn Execute(TParam& param) = 0;
};

//  Inheriting from ICallback. This object is a callback for a particular function (defined by the return type and parameter) on a particular class (TClass).
template<typename TClass, typename TReturn, typename TParam> class Callback : public ICallback<TReturn, TParam>
{
    typedef TReturn(TClass::*Function)(TParam& param);
    TClass& m_rObject;
    Function m_pFunction;

public:
    Callback(TClass& rObject, Function pFunction)
        : m_rObject(rObject)
        , m_pFunction(pFunction)

    inline TReturn Execute(TParam& param)
    {
        return (m_rObject.*m_pFunction)(param);
    }
};

template<typename TReturn, typename TParam> class Delegate
{
    ICallback<TReturn, TParam>* m_pCallback;  //Pointer to interface, so we can story any Callback inheriting from ICallback<TReturn, TParam>.

    Delegate() : m_pCallback(NULL);
    ~Delegate()
    {
        ClearCallback();
    }

    template<typename TClass> SetCallback(TClass& rObject, TReturn(TClass::*pFunction)(TParam& param) )
    {
        ClearCallback();
        m_pCallback = new Callback<TClass, TReturn, TParam>(rObejct, pFunction);
    }

    void ClearCallback()
    {
        if(m_pCallback != NULL)
        {
            delete m_pCallback;
        }
    }

    TReturn Execute(TParam& param)
    {
        if( m_pCallback != NULL)
        {
            return m_pCallback->Execute(param);
        }
        return (TReturn)0;
    }
};

Here is the general usage

class MyClass
{
    //Create some struct to pass relevant information to the callback function
    struct EventObject { int m_EventData };

    //Declare the delegate
    //Note: To handle the event outside of MyClass (maybe someone is listening to MyClass's 
    Delegate<void, EventObject> m_OnEventDataChanged;

    public:
    //On construction, set the callback.
    MyClass()
    {
        m_OnEventDataChanged.SetCallback(*this, &MyClass::OnEvent);
    };
    
    //Declare the callback event.
    void OnEvent(EventObject& eventObject)
    {
        //Do something 
    }

    //During some function, some event happens
    void SetEventData(int newEventData)
    {
        m_EventData = newEventData;
        EventObject eventObject;
        eventObject.m_EventData = m_EventData;
        m_OnEventDataChanged.Execute(eventObject);
    }
};
This is a fairly primitive implementation of a delegate class. I intentionally left it this way so that others reading this might take it and make it their own. Here's a list of some things you may want to do to improve it's functionality.

  • Overload the Delegate's () operator to make it a functor (or Function Object, according to Wikipedia). Just have the () operator take the TParam as the parameter and essentially wrap Execute(). This will make it feel more like a C# delegate.
  • Use a smart pointer to manage the Callback object.
  • Use a container to contain multiple callbacks. SetCallback() becomes AddCallback(). Then, multiple objects can listen to a single event. And if you're going to go down this route, you could:
    • Add a RemoveCallback().
    • Overload += and -= to add or remove callbacks (this makes it feel a bit more like a C# delegate, but can make things messy since we have to pass two parameters for setting the callback (the function, and the object on which the function will be called). I'd generally say this one isn't worth it, unless you're doing some heavy rewriting.
  • Add methods for suppressing the event. This way, the client could call m_OnEventDataChanged.Suppress() and m_OnEventDataChanged.Unsuppress() to suppress the event. This can be handy, if you make SetEventData() have a second bool parameter that when true (and by default is false), won't fire the event.
  • If you go with making it possible to suppress the event, you could also make a SuppressGuard class. The class would construct taking in a reference or pointer to the delegate to be suppressed, and automatically call Suppress() on it. The destructor of the SuppressGuard would then call Unsuppress() The usage for suppressing goes down to a single line ("SuppressGuard suppressGuard(m_OnEventDataChanged, bSuppress);") and you'll never cause a bug by forgetting to suppress it.

That's about it. If you're knowledgeable on the subject of delegates in C++, feel free to chime in and point out flaws or places for improvement. Everybody needs a good code review, even me. =P

No comments:

Post a Comment