65.9K
CodeProject 正在变化。 阅读更多。
Home

易于使用的事件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.37/5 (19投票s)

2005年10月30日

2分钟阅读

viewsIcon

51144

downloadIcon

409

本文描述了一个用标准 C++ 编写的易于使用的事件类。

引言

本文描述了一个用标准 C++ 编写的易于使用的事件类。它非常简单,易于理解和使用。你几乎没有犯错的机会。参数数量没有限制。你可以连接到成员函数、函数对象或全局函数。我已经修改了它,使其能够使用 GCC 编译。演示文件夹中包含一个 Dev-C++ 项目文件。

一个非常简单的例子

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

如上例所示,它非常简单。你需要做的就是定义一个事件,然后调用它的 addListener 方法将其与目标对象连接起来。fire 方法用于触发一个事件。

唯一令人困惑的是

EVENT_TYPE(TextChanged, (string s), (s));

这代表什么意思?

实际上很简单,宏展开后的结果是一个名为 TextChanged 的类,例如

class TextChanged {
public:
    void fire(string s);
};

(string s)(s) 的括号不应省略。(string s) 称为参数列表,它指定了 fire() 方法接受的参数的类型和名称。(s) 称为参数列表,它指定如何调用 fire() 方法,例如 fire(s)

连接管理

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 在事件和目标对象之间建立连接,将双方链接起来。

如果你将 addListener 的返回值分配给一个连接对象,当该连接对象的生命周期结束时,事件和目标对象之间的连接将中断,因此双方之间不再有关系。

在连接对象上调用 disconnect 方法具有相同的效果。如果你没有将 addListener 的返回值分配给某个连接对象,则在事件对象生命周期结束之前,事件和目标对象之间存在一个匿名连接,该连接将中断。

事件和目标对象之间的连接不会自动中断。如果连接没有中断,而目标对象不再存在,则触发事件会导致问题,因此请务必小心。

为了确保目标对象生命周期结束后,目标对象和事件之间没有连接,你可以选择在目标对象死亡之前调用连接对象的 disconnect 方法,或者将连接作为目标对象的一部分。在大多数情况下,后者更可取。

历史

  • 错误修复 - 2005-11-07。
© . All rights reserved.