Click here to Skip to main content
Click here to Skip to main content

Emulating C# delegates in Standard C++

, 24 Feb 2004
Rate this:
Please Sign up or sign in to vote.
Yet another way create C#-style delegates in C++ using a mixture of templates and polymorphism

Introduction

C# has a language feature called 'delegates' which makes it easy to detach the originator of an event from the ultimate handler. They perform essentially the same role as function pointers in C and pointers to member functions in C++, but they are much more flexible. In particular they can be used to point to any function on any object, as long as it has the appropriate signature.

This article explains my approach for providing the same functionality using only standard C++. There are many worthy alternatives which you can find easily by Googling with "C++ delegates". The focus of my effort was to obtain a syntax very similar to that in Managed C++ and C#.

Background

If you already know everything you want to know about delegates, please skip this section.

Delegates are not a new idea. Borland's Delphi and C++ Builder products have used them from the outset to support the Visual Component Library, though they are called 'method pointers' in Delphi, and 'closures' in Builder (they are the same thing as far as I know). Basically a closure is an OO function pointer. Internally it simply holds the address of the function to be called plus the address of the object on which it is being called (i.e. the hidden 'this' parameter which is passed to the function).

The important point is this: being agnostic about which other class is going to handle the events a class generates is a key factor in making Delphi's wonderful component-based visual development framework possible. Reduced coupling between classes is a Good Thing.

Now C#-style delegates provide the same service to .NET languages but, sadly, Standard C++ does not have them. Pointers to member functions are very restricted in comparison, though they have been used in conjunction with macros in libraries such as Borland's OWL in the past, and (I think) with Trolltech's Qt today. And that's why I'm writing this article.

With the .NET delegates, you can even attach several handlers to a single event. They will all be called (sequentially) when the delegate is invoked. This means you can safely attach your handler to an event without breaking someone else's connection. I haven't used this feature yet, but I recognize the potential. However, I do question whether such a feature can be implemented efficiently. Invoking Borland's single-cast closures boils down to a couple of opcodes which push 'this' and call the function, so they are powerful but still cheap to use. [It's a pity they're not in the Standard...] But once you start maintaining a dynamic collection of targets, life gets more complicated. Ideally, .NET should have really efficient single-cast delegates, and implement multicast delegates in terms of those - best of both worlds. For all I know, that's what it does.

.NET distinguishes between 'delegates' and 'events'. A delegate is a glorified function pointer, as I have said; an event is an application of a delegate - a member of a class to which you can assign the addresses of handlers which will be called when the class invokes the delegate. I admit to finding the distinction unhelpful - how else would delegates be used, anyway? I sometimes use the terms interchangeably.

C# and Managed C++ have a rather tidy syntax for assigning handlers to a delegate/event:

mnuPopup->Popup += new System::EventHandler(this, mnuPopup_Popup);

When the context menu, mnuPopup, is displayed by the user, it will invoke its Popup event/delegate. This will then call the handler I have implemented in my form, mnuPopup_Popup. I have tried to preserve something like this syntax in my code.

Understanding the code

Enough waffling!! Let's talk about the code. If you couldn't care less about how it works, just skip this section and head straight for 'Using the code'

The design is intended to satisfy the following constraints:

  • The delegate can call non-static member functions on an object of any class, including virtual functions.
  • The delegate can call static member functions of any class, and free functions.
  • The delegate is multicast.
  • The delegate is easy to use (i.e. keeps close to the C# syntax).

There are four parts to my solution:

  1. A base class to specify signature without reference to any actual classes. It has a pure virtual overload of operator(). This is a private nested class.
  2. An outer class actually used by clients. It invokes operator() on a collection of derivatives of the base class.
  3. Derived classes which implement the overloaded operator() declared in the base class. This is a public nested template class, and allows us to specify the type of an event target.
  4. A derived class to handle static and free functions. This is a public nested class.

The base class looks like this (Return and Arg1 are types from the outer class):

class Base
{
  public:
    virtual ~Base() { }
    virtual Return operator()(Arg1) = 0;
};

The derived class for non-static functions looks like this (Return and Arg1 are types from the outer class):

template <typename Class>
class T : public Base
{
  // Signature applied to a pointer-to-member for target class.
  typedef Return (Class::*Func)(Arg1);

  private:
    Class* mThis; // Pointer to the object we are delegating to.
    Func   mFunc; // Address of the function on the delegate object.

  public:
    T(Class* aThis, Func aFunc) : mThis(aThis), mFunc(aFunc) { }

    virtual Return operator()(Arg1 arg1)
    {
      return (mThis->*mFunc)(arg1);
    }
};

The derived class for static and free functions looks like this (Return and Arg1 are types from the outer class):

class S : public Base
{
  typedef Return (*Func)(Arg1);

  private:
    Func mFunc; 

  public:
    S(Func aFunc) : mFunc(aFunc) { }
  
    virtual Return operator()(Arg1 arg1)
    {
      return mFunc(arg1);
    }
};

The outer class looks like this (with many details missing):

template <typename Return, typename Arg1>
class Event 
{
  private:
    std::vector<Base*> mPtrs;
    class Base { ... };
 
  public: 
    template <typename Class>
    class T : public Base { ... }; // Non-static
    class S : public Base { ... }; // Static
    
    // Add a new target (callee) to our list.
    Event& operator+=(Base* aPtr)
    {
      mPtrs.push_back(aPtr);
      return *this;
    }

    // Call all the targets - there will be horrible undefined behaviour
    // if the callee object no longer exists. You have been warned!
    Return operator()(Arg1 arg1)
    {
      // There are problems here:
      // 1. Which result should the multicast return? 
      // For now I say the last called item.
      // 2. We need to can't store a temporary when Return is void.
      typename std::vector<Base*>::iterator end = mPtrs.end();
      for (typename std::vector<Base*>::iterator i = mPtrs.begin();
        i != end; ++i)
      {
        // Probably a specialisation for Return == void would be better.
        if ((i + 1) == end)
          return (*(*i))(arg1);
        else
          (*(*i))(arg1); 
      }
    }
};

There is still some work to do. I want to make it safe to copy these objects, and I need to come up with something for multicast delegates with a signature that returns are value. Perhaps I could return a vector of results. I also need to duplicate the template to cope with two or more arguments to the signature.

No doubt this implementation is very slow compared to function pointers but the usual application of events is GUI work, so speed is not so critical. I'd be very interested to see what the underlying implementation of .NET delegates and events looks like...

It seems that my Event class would be most useful as a public member of any class wishing to expose an event. This breaks encapsulation, but I think the syntax for adding targets would get pretty hairy otherwise. We want to prevent clients of a class using Event from invoking operator() on it. Perhaps a simple adapter would do the job - it would be a public member passing calls on to a private Event member, but not exposing operator().

Using the code

To try this out, download the demo code. Everything is contained in a single file to make life easy. Just compile it and run it from the command prompt. This code was developed on g++ 3.2. I'm keen to know which other compilers like it, and which ones don't, so please let me know.

struct TShapes
{
  virtual void Square(int i)   
    { std::cout << "TShapes::Square:          " << i << std::endl; }   
  void Triangle(int i)         
    { std::cout << "TShapes::Triangle:        " << i << std::endl; }   
};

struct TDerivedShapes : TShapes
{
  virtual void Square(int i)   
    { std::cout << "TDerivedShapes::Square:   " << i << std::endl; }  
};

struct TThings
{
  void Thing1(int i)           
    { std::cout << "TThings::Thing1:          " << i << std::endl; }   
  static void Thing2(int i)    
    { std::cout << "TThings::Thing2:          " << i << std::endl; }   
};

void Free(int i)               
    { std::cout << "Free:                     " << i << std::endl; } 

int main()
{
  // As usual, a typedef makes life easier when using templates.
  typedef Event<void, int>  MyEvent; 
  MyEvent event;

  TShapes        shapes;
  TDerivedShapes shapes2;
  TThings        things;  

  // These items get the ball roling.
  event += new MyEvent::T<TShapes>(&shapes, &TShapes::Square);
  event += new MyEvent::T<TShapes>(&shapes, &TShapes::Triangle);

  // This item shows that virtual functions are handled correctly.
  event += new MyEvent::T<TShapes>((TShapes*)&shapes2, &TShapes::Square);

  // This item shows that inherited functions are handled correctly.
  event += new MyEvent::T<TDerivedShapes>(
    &shapes2, &TDerivedShapes::Triangle);

  // This item shows that the Event object 
  // can hold a truly heterogeneous 
  // collection of targets.
  event += new MyEvent::T<TThings>(&things, &TThings::Thing1);

  // This item shows that static functions are handled correctly.
  event += new MyEvent::S(&TThings::Thing2);
  
  // This item shows that free functions are handled correctly.
  event += new MyEvent::S(&Free);
   
  // Invoke the multicast event 
  std::cout << "<multicast>" << std::endl; 
  event(100);  
  std::cout << "</multicast>" << std::endl;
  
  return 0; 
}

This is what you should see when you run the program:

<multicast>
TShapes::Square:          100
TShapes::Triangle:        100
TDerivedShapes::Square:   100
TShapes::Triangle:        100
TThings::Thing1:          100
TThings::Thing2:          100
Free:                     100
</multicast>

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Arnold the Aardvark

United Kingdom United Kingdom
No Biography provided

Comments and Discussions

 
GeneralI have a *very* efficient implementation of closures, if you're interested PinmemberDon Clugston2-Mar-04 12:35 
GeneralRe: I have a *very* efficient implementation of closures, if you're interested Pinmemberboutblock3-Mar-04 8:12 
GeneralRe: I have a *very* efficient implementation of closures, if you're interested PinmemberArnold the Aardvark3-Mar-04 9:08 
GeneralRe: I have a *very* efficient implementation of closures, if you're interested PinmemberDon Clugston3-Mar-04 14:15 
GeneralRe: I have a *very* efficient implementation of closures, if you're interested PinmemberArnold the Aardvark4-Mar-04 9:12 
GeneralRe: I have a *very* efficient implementation of closures, if you're interested PinmemberDon Clugston6-Jul-04 15:07 
GeneralRe: I have a *very* efficient implementation of closures, if you're interested PinmemberSecondAct29-Nov-05 15:03 
GeneralRe: I have a *very* efficient implementation of closures, if you're interested PinmemberDon Clugston29-Nov-05 23:11 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.141015.1 | Last Updated 25 Feb 2004
Article Copyright 2004 by Arnold the Aardvark
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid