Click here to Skip to main content
15,888,968 members
Articles / Programming Languages / C++

Emulating C# delegates in Standard C++

Rate me:
Please Sign up or sign in to vote.
4.55/5 (7 votes)
24 Feb 20045 min read 112.5K   1.2K   43  
Yet another way create C#-style delegates in C++ using a mixture of templates and polymorphism
//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
// Event implementation
#include <vector> 

// Event class - invoke operator() on a collection of objects.
// Note that only one argument is supported for the parameters of
// the target signature. However, it is trivial to extend this to
// multiple arguments by having multiple templates similar to this
// one.
template <typename Return, typename Arg1>
class Event
{
  private:
    // Base class - specify the signature.
    class Base
    {
      public:
        // Required for polymorphic destruction to work properly.
        virtual ~Base() { };

        // Target signature. Invoked through base class pointers.
        virtual Return operator()(Arg1) = 0;
    };
    
  public:  
    // Derived class - parametrise the implementation so we can specify the 
    // target class in client code. 
    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) { }
     
        // Invoke the function throught the pointer-to-member.
        virtual Return operator()(Arg1 arg1)
        {
          return (mThis->*mFunc)(arg1);
        }
    };
    
  public:  
    // Derived class - this is not parametrised and is intended for 
    // static and free functions.
    class S : public Base
    {
      typedef Return (*Func)(Arg1);

      private:
        Func mFunc; 

      public:
        S(Func aFunc) : mFunc(aFunc) { }
      
        // Invoke the function throught the pointer-to-member.
        virtual Return operator()(Arg1 arg1)
        {
          return mFunc(arg1);
        }
    };
    
  private:
    // Use a vector to make this similar to C# multicast events.
    std::vector<Base*> mPtrs;

  private:
    // Can't allow copying unless we modify the class to make a deep
    // copy of each of the pointers it holds. Base class would need 
    // a virtual Clone() method to allow polymorphic copying.
    Event(const Event&);
    Event& operator=(const Event&);

  public:
    // Compiler seems to require this because other default constructors
    // have been made explicit.
    Event() { }

    // Clean up the garbage.
    ~Event()
    {
      typename std::vector<Base*>::iterator end = mPtrs.end();
      for (typename std::vector<Base*>::iterator i = mPtrs.begin();
        i != end; ++i)
      {
        delete (*i);
      }
    }

    // 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? 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); 
      }
    }
};




//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
// Demo code
#include <iostream> 

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; 
}



By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

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


Written By
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions