Introduction
This article describes an easy to use event class written in standard C++. It is very simple, easy to understand and use. You hardly have a chance to make a mistake. There is no limit on the number of parameters. You can connect to member functions, function objects, or global functions. I have revised it to compile with GCC. There is a Dev-C++ project file in the demo folder.
A very simple example
#include <iostream>
#include <string>
#include "Event.hpp"
using namespace std;
class Edit {
string s_;
public:
EVENT_TYPE(TextChanged, (string s), (s));
TextChanged changed;
void set(string s)
{
s_ = s;
changed.fire(s_);
}
};
class Dialog {
Edit edit_;
public:
void OnEditChanged(string s)
{
cout << "text in edit is : " << s << endl;
}
Dialog()
{
edit_.changed.addListener(this, &Dialog::OnEditChanged);
}
void input(string s)
{
edit_.set(s);
}
};
void example1()
{
Dialog dlg;
dlg.input("some text");
dlg.input("another text");
}
As the example above suggests, it is very simple. What you need to do is just define an event, and then invoke its addListener
method to connect it with the target object. The fire
method is used to fire an event.
The only thing confusing is:
EVENT_TYPE(TextChanged, (string s), (s));
What dose this mean?
In fact, it's simple, the result after expanding of the macro is a class named TextChanged
, like:
class TextChanged {
public:
void fire(string s);
};
The parenthesis of (string s)
and (s
) should not be omitted. (string s)
is called parameters list, which specifies the types and names of parameters that fire()
method accepts. (s
) is called arguments list which specifies how to invoke the fire()
method, like fire(s)
.
Connection management
EVENT_TYPE(SomeEvent, (int x, int y), (x, y));
SomeEvent event1;
class Foo {
public:
void operator()(int a, int b)
{
cout << "function object: " << a << '+'
<< b << '=' << a + b << endl;
}
};
void bar(int a, int b)
{
cout << "function: " << a << '+' << b << '=' << a + b << endl;
}
void example2()
{
Foo foo1;
SomeEvent::Connection c1(event1.addListener(foo1));
SomeEvent::Connection c2;
c2 = event1.addListener(bar);
event1.fire(1, 2);
{
Foo foo2;
SomeEvent::Connection c4(event1.addListener(foo2));
event1.addListener(bar);
event1.fire(3, 4);
}
c1.disconnect();
event1.fire(5, 6);
}
void example3()
{
SomeEvent::Connection c;
{
SomeEvent event2;
c = event2.addListener(bar);
event2.fire(9, 9);
}
}
addListener
builds a connection between an event and a target object, linking each side.
If you assign the return value of addListener
to a connection object, when the life time of this connection object is over, the connection between the event and the target object will break up, hence, there is no relation between each side.
Invoking the disconnect
method on the connection object has the same effect. If you did not assign the return value of addListener
to a certain connection object, there is an anonymous connection between the event and the target object until the life time of the event object is over, and this connection will break up.
The connection between an event and a target object will not break up automatically. While the connection has not broken up and the target object no longer exists, firing an event will cause a problem, so be cautious.
In order to ensure that there is no connection between target objects and events after the target object's life time, you can either call the disconnect
method of the connection object before the target object's death, or make the connection as part of the target object. In most cases, the latter is more preferable.
History