Easy C++ Delegates

As I mentioned in my previous post, I was working on delegates for C++.

The first article I read was recommended by one of my programming TAs. The author’s main concern was portability and performance, so he special-cased extensively throughout his code, in order to make sure the code works perfectly fine across multiple compilers while still yielding optimal performance. This resulted in very verbose and convoluted code. I was half way through comprehending all his code and gave up. The article itself, however, is certainly worth reading; it covers in details the difference between function pointers and member pointers and points out why they are not easy (also not too hard) to deal with correctly.

The second post I read was a follow-up to the first one. The code is easier to read; however, the author used macros extensively to avoid code duplication in his source files, so trying to untangle the macros in mind while reading the code is not a light task. Fortunately, the author provided a clean and “untangled” example in the post, which is just enough to understand the logic behind it.

I found the last article by Googling “C++ delegates”. It is the easiest of the three (in terms of sample code), since the author’s first concern was simplicity instead of performance, and it was just what I needed. He used polymorphism to handle function pointers and member function pointers differently. His sample code helped me the most in coming up with my own delegate class design. I didn’t read through the entire article, though, because it was kind of hard for me to read.

As mentioned before, I started my research on delegates because I wanted to implement a robust event/signal system for my next school game project at DigiPen. Thus, one of my concerns was to make the code as simple as possible, so that it would be easy for me to explain how it works to my teammates.

Here I’m going to show you what I’ve come up with. In order to make things simple, I am going to show you simplified version of the delegate class, where it can only point to a function with one parameter. It can be easily generalized with templates to deal with any number of parameters once you understand it.

The Callback Class

The responsibility of the Callback is to provide an interface for a delegate to invoke the underlying function, be it a regular function (static function) or a member function associated with a pointer that holds the object address to be used as the value of the this pointer.

template <typename Ret, typename Param0>
class Callback
{
  public:
    virtual Ret invoke(Param0 param0) = 0;
};

The Ret type is the return type of the function, and the Param0 type is the type of the one parameter passed to the function. We are going to extend this class and create two subclasses, one is responsible for static function callbacks, and one responsible for member function callbacks.

The StaticFucntionCallback Class

Static function callbacks are easy to implement, where all you have to do is declare an appropriate function pointer, and invoke it using the function pointer invocation syntax.

template <typename Ret, typename Param0>
class StaticFunctionCallback : public Callback<Ret, Param0>
{
  private:
    Ret (*func_)(Param0);
  
  public:
    StaticFunctionCallback(Ret (*func)(Param0))
      : func_(func)
    {}
    
    virtual Ret invoke(Param0 param0)
    {
      return (*func_)(param0);
    }
};

The MethodCallback Class

Member function (method) callbacks require just a little more work, where we have to keep track of which object address to use for the this pointer while invoking the method.

template <typename Ret, typename Param0, typename T, typename Method>
class MethodCallback : public Callback<Ret, Param0>
{
  private:
    void *object_;
    Method method_;
  
  public:
    MethodCallback(void *object, Method method)
      : object_(object)
      , method_(method)
    {}
    
    virtual Ret invoke(Param0 param0)
    {
      T *obj = static_cast<T *>(object_);
      return (obj->*method_)(param0);
    }
};

Note that I used the template type Method to handle all possible member function types. It is not type-safe; I could have used some non-type template parameter like Ret (T::*method)(Param0) and take extra care of it, just as in the aforementioned articles. However, my goal here is to make things simple, so I just use a regular type parameter.

The Delegate Class

Finally, we’re going to wrap Callback objects into the Delegate class. The constructor is overloaded to handle both cases: static functions and member functions. The corresponding Callback object is created, and the function invocation operator would call the correct version of the invoke method through polymorphism.

template <typename Ret, typename Param0>
class Delegate
{
  private:
    Callback<Ret, Param0> *callback_;
    
  public:
    Delegate(Ret (*func)(Param0))
      :callback_(new StaticFunctionCallback<Ret, Param0>(func))
    {}
    
    template <typename T, typename Method>
    Delegate(T *object, Method method)
      :callback_(new MethodCallback<Ret, Param0, T, Method>(object, method))
    {}
    
    ~Delegate(void) { delete callback_; }
    
    Ret operator()(Param0 param0)
    {
      return callback_->invoke(param0);
    }
};

Some Sample Client Code

We’re pretty much done here. Now I’ll just show you how you would use this delegate class in the client code.

First, I declare four classes with single inheritance and multiple inheritance (just to show that this works in both cases), as well as a static function foo.

class A
{
  public:
    
    virtual int foo(int p)
    {
      std::cout << "A::foo(" << p << ")" << std::endl;
    }
};

class B : public A
{
  public:
    virtual int foo(int p)
    {
      std::cout << "B::foo(" << p << ")" << std::endl;
    }
};

class C
{
};

class D : public C, public A
{
  public:
    virtual int foo(int p)
    {
      std::cout << "D::foo(" << p << ")" << std::endl;
    }
};

int foo(int x)
{
  std::cout << "foo(" << x << ")" << std::endl;
}

Finally, in the main function, four delegates are created to demonstrate different scenarios. I’ve tested it with GNU g++ compiler and Microsoft Visual Studio C++ Compiler 2008 and it worked perfectly.

int main(void)
{
  A *a = new A();
  A *b = new B();
  A *d = new D();
  
  //static function
  Delegate<int, int> d1(foo);
  
  //member function
  Delegate<int, int> d2(a, &A::foo);
  
  //member function + subclass instance
  Delegate<int, int> d3(b, &A::foo);
  
  //member function + subclass instance + multiple inheritance
  Delegate<int, int> d4(d, &A::foo);
  
 
  d1(100); //"foo(100)"
  d2(200); //"A::foo(200)"
  d3(300); //"B::foo(300)"
  d4(400); //"D::foo(400)"
  
  return 0;
}

About Allen Chou

Physics / Graphics / Procedural Animation / Visuals
This entry was posted in C/C++, Design Patterns, Programming. Bookmark the permalink.

4 Responses to Easy C++ Delegates

  1. Pingback: 委托模式 – 守望科技博客

  2. Nisnor says:

    Really helpfull article 😉 It combines several great sources and one custom delegate implementation. Thank you so much 😀

  3. Awesome work, this article really helped me get over a hurdle with neat event handling for my educational game engine. Professionally everyone usually uses Don’s fastdelegate which is great but unreadable.

    One modification I performed to the code you supplied was implementing a default no argument constructor to the Delegate and then splitting out the existing ctor to an AddCallback(T *object, Method method) public function to set up the delegate for use.

    Then I reference count the callback_ member so one delegate can register many member functions for subscriber type usage.

  4. Jason Wong says:

    Hi, I find your blog by searching c++ delete on google.
    After reading the articles you mentioned and your code, I write the following implementation for my c++ callback.

    class NullClass;
    template class functor
    {
    private:
      typedef R (*functor_fp_0)();
      typedef R (NullClass::*memFun_0)();
    
    public:
      functor() { }
      ~functor() { }
    
      functor(functor_fp_0 a)
      {
        memcpy((void *) &m_fp, (void *) &a, sizeof(functor_fp_0));
      }
      template functor(R (Y::*a)())
      {
        memcpy((void *) &m_memFun, (void *) &a, sizeof(memFun_0));
      } 
      void operator = (functor_fp_0 a) { 
        memcpy((void *) &m_fp, (void *) &a, sizeof(functor_fp_0));
      } 
      template void operator = (R (Y::*a)()) 
      { 
        memcpy((void *) &m_memFun, (void *) &a, sizeof(memFun_0));
      } 
      R operator () () {
        return (m_fp)();
      }
      template R operator () (X *x) {
        return (((NullClass *) x)-&gt;*(m_memFun))();
      }
    
    private:
      functor_fp_0 m_fp;
      memFun_0 m_memFun;
    };
    

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.