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

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

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.73/5 (7投票s)

2004 年 9 月 12 日

2分钟阅读

viewsIcon

49586

downloadIcon

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来指定带有一个参数的信号;对于两个和三个参数,分别有CppSignal2CppSignal3。对于标准的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;

信号和槽的实现

CppSignalCppSlot的实现与“C++中的泛型观察者模式和事件”中的CppEvent非常相似。

© . All rights reserved.