Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

An easy to use event

4.37/5 (21 votes)
6 Nov 20052 min read 1   409  
This article describes an easy to use event class written in standard C++.

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

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

// 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 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

  • Bug fixed - 2005-11-07.

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