Introduction
It is 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 function, function object or global function.
I have revised it to compile with GCC.
There is a Dev-C++ project file in the demo folder.
BUG fixed! 2005-11-07
A very simple example
// example1 #include <iostream> #include <string> #include "Event.hpp" using namespace std; class Edit { string s_; public: // define event type 'TextChanged' EVENT_TYPE(TextChanged, (string s), (s)); TextChanged changed; void set(string s) { s_ = s; // fire 'changed' event when the content of Edit is altered changed.fire(s_); } }; class Dialog { Edit edit_; public: void OnEditChanged(string s) { cout << "text in edit is : " << s << endl; } Dialog() { //edit_.changed.addListener(this, OnEditChanged); // vc7.1 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 do is just defining an event, and then invokes its addListener method to connect it with the target object.
fire method is used to fire event.
The only thing confusing is
EVENT_TYPE(TextChanged, (string s), (s));
What dose these mean?
In fact, it's simple, the result after expanding of the macro is a class named TextChanged, just 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 accept.
(s) is called arguments list which specifies how to invoke fire() method, like fire(s).
Connection management
EVENT_TYPE(SomeEvent, (int x, int y), (x, y)); SomeEvent event1; // function object class Foo { public: void operator()(int a, int b) { cout << "function object: " << a << '+' << b << '=' << a + b << endl; } }; // ordinary function void bar(int a, int b) { cout << "function: " << a << '+' << b << '=' << a + b << endl; } void example2() { Foo foo1; // connect to a function object SomeEvent::Connection c1(event1.addListener(foo1)); SomeEvent::Connection c2; // connect to an ordinary function c2 = event1.addListener(bar); //SomeEvent::Connection c3(c1); // error: copy construction is not permitted //c1 = c2; // error: assignment is not permitted event1.fire(1, 2); { Foo foo2; SomeEvent::Connection c4(event1.addListener(foo2)); // create an anonymous connection event1.addListener(bar); event1.fire(3, 4); // c4 break up automaticly } c1.disconnect(); event1.fire(5, 6); } // connection's lifetime longer than event, no problem! void example3() { SomeEvent::Connection c; { SomeEvent event2; c = event2.addListener(bar); event2.fire(9, 9); } // you can invoke c.disconnect() explicitly or // rely on c's destructor to do it }
addListener builds a connection between event and 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 event and target object will break up, hence, there is no relation between each side.
Invoking disconnect method on connection object has the same effect.
If you did not assign the return value of addListener to certain connection object, there is an anonymous connection between event and target object, until the life time of event object is over, this connection will break up.
The connection between event and target object will not break up automatically. While the connection has not broken up and the target object is no longer exists, firing event will cause problem, so be cautious.
in order to ensure that there is no connection between target object and event after the target object's life time, you can either call the disconnect method of connection object before the target object's death, or make the connection as part of the target object. In most case, the latter is more preferable.