Introduction
This article introduces a very simple and comfortable event mechanism in C++. In many cases, a delegate in C++ is hard to understand because of its complexity. The attached code is a sample that uses the combination of the C# delegation style and Java's inner class callback style. It can show you an example of an easy event mechanism.
Event Processing
struct ISampleEventHandler : public IEventHandler
{
virtual void callback( IUnknown* object, const SampleEventArgs& e ) = 0;
typedef SampleEventArgs _EventArgs; };
template<typename T>
class Event
{
public:
void invoke(IUnknown* object, const EventArgs& e = EventArgs::Empty)
{
std::set<T*>::iterator iter;
for (iter = m_EventHandler.begin();
iter != m_EventHandler.end();
++iter)
{
(*iter)->callback(object, static_cast<const T::_EventArgs&>(e));
}
}
void operator+=(T* eventHandler)
{
m_EventHandler.insert(eventHandler);
}
void operator-=(T* eventHandler)
{
m_EventHandler.erase(eventHandler);
}
protected:
std::set<T*> m_EventHandler;
};
Using the Code
We need an abstract parent class to identify the event sender.
class IUnknown
{
public:
virtual ~IUnknown() {}
virtual std::string name() = 0;
};
Here is the event sending class:
class Callee : public IUnknown
{
public:
Event<ISampleEventHandler> SampleEvent;
public:
void test()
{
SampleEventArgs e;
e.sampleData = 10;
e.sampleData2 = 20;
SampleEvent.invoke(this, e);
}
std::string name() { return "callee"; }
};
Here is the event receiving class:
class Caller : public IUnknown
{
public:
Caller()
{
m_Callee.SampleEvent += &sampleEventHandler;
}
~Caller()
{
m_Callee.SampleEvent -= &sampleEventHandler;
}
void test()
{
m_Callee.test();
}
private:
void print(IUnknown* object, const SampleEventArgs& e)
{
std::cout << object->name().c_str() << std::endl;
std::cout << e.sampleData << std::endl;
std::cout << e.sampleData2 << std::endl;
}
std::string name() { return "caller"; }
private:
class SampleEventHandler : public ISampleEventHandler
{
void callback( IUnknown* object, const SampleEventArgs& e )
{
Caller* outer = reinterpret_cast<Caller*>((char*)this -
offsetof(Caller, sampleEventHandler));
outer->print(object, e);
}
} sampleEventHandler;
private:
Callee m_Callee;
};
Points of Interest
You can easily find the reason why using the delegate is nice. It can break the cyclic dependency of each class - the 'Caller' depends on the 'Callee' but the 'Callee' does not have dependency with any classes.
History
- 2007. 03. 29 - Initially released.