使用信号和槽模式在 C++ 中实现委托






3.73/5 (7投票s)
2004 年 9 月 12 日
2分钟阅读

49586

771
本文介绍了使用信号和槽模式在C++中高效实现委托的方法。
引言
在我之前的文章“C++中的泛型观察者模式和事件”中,我们讨论了经典的“观察者模式”及其在C++中使用模板的实现。在那篇文章中介绍的事件类CppEvent
允许我们将松散耦合的类绑定在一起,通过将包含CppEvent
的一个类连接到其他类的成员函数。成员函数可以具有任意数量和类型的参数,并且模型和视图类不需要任何关系或特殊的继承。
#include "CppEvent.h" // declare view class class MyView { public: bool updateFun(int p) { cout << "on model updated with " << p << endl; return true; } }; // declare model class containing event class MyModel { public: CppEvent1<bool,int> event; }; // create viewers MyView* view = new MyView; // create model MyModel model; // attach view member function to the model CppEventHandler h = model.event.attach(view,& MyView::updateFun); // emit event and notify views model.event.notify(1); // detach the view from the model model.event.detach(h);
这里唯一的问题是,如果我们不先将视图从模型中分离,就不能删除视图,否则在下一次调用事件通知时,可能会导致应用程序崩溃。当我们动态创建和删除许多模型时,这就会变得很烦人。为了解决这个问题,我们需要一个委托,在视图被删除时,它会自动从模型中移除自身。为了完成此任务,我们使用信号和槽的概念。这个概念已在Trolltech Qt库和Boost C++库中引入。
使用信号和槽
为了演示信号和槽的工作方式,我们创建一个包含CppSignal
成员的模型类和一个包含CppSlot
的视图类。
// model class #include "CppSlot.h" // define model class class MyModel { public: void modelChanged() { m_signal.emit_sig(1); } // signal with the signature of corresponding slot function // CppSignal<RETURNTYPE,1-ST ParameterType> CppSignal1<int,int> m_signal; };
请注意,我们使用CppSignal1
来指定带有一个参数的信号;对于两个和三个参数,分别有CppSignal2
和CppSignal3
。对于标准的C++编译器,我们可以避免这种名称的重复,这样做是为了与仍在使用的VC++ 6兼容。
// view class class MyView { public: MyView() : m_slot(this,&MyView::onModelChanged) {} int onModelChanged(int i) { cout << "model changed:" << i << endl; return 1; } // slot with specified view type and signature of callback // function CppSlot1<MyView,int,int> m_slot; };
请注意,槽成员在视图类的构造函数中初始化。它使用指向视图类和成员函数的指针进行初始化。现在,要将模型连接到视图,我们需要将它的信号与槽连接起来。
// create model conaining CppSignal MyModel model; // create view with CppSlot MyView view; // connect signal to slot model.m_signal.connect(&view.m_slot); model.modelChanged();
现在我们可以创建另一个视图并将其连接到同一个模型。
// create another view and connect it to the model MyView1* view1 = new MyView1(); model.m_signal.connect(&view1->m_slot);
为了跟踪视图回调函数的返回值,我们可以使用收集器函数对象。例如,一个计数返回值的对象将是
class ResponseAccumulator { public: typedef int return_type; template <Typename iterator>int operator () (iterator begin,iterator end) { int sum = 0; for(iterator it=begin; it!=end; ++it) sum+=*it; return sum; } };
当它作为emit_sig
方法的第二个参数传递时,它将返回视图响应的总和。
// use ResponseAccumulator function object // to collect responses from models int res = model.m_signal.emit_sig(3,ResponseAccumulator()); cout << "sum of responses is " << res << endl;
现在我们可以删除其中一个视图,响应的总和也会相应地改变。
// now we can delete second model and it // automatically disconnect itself from the // model delete view1; res = model.m_signal.emit_sig(3,ResponseAccumulator()); cout << "new sum of responses is " << res << endl;
信号和槽的实现
CppSignal
和CppSlot
的实现与“C++中的泛型观察者模式和事件”中的CppEvent
非常相似。