分类: C/C++
2010-11-28 20:36:01
将 Qt 的信号槽系统与 Boost.Signals 结合使用
实际上,将 Qt 的信号槽系统与 Boost.Signals 结合在一起使用并非不可能。通过前面的阐述,我们都知道了二者的不同,至于为什么要将这二者结合使用,则是见仁见智的了。这里,我们给出一种结合使用的解 决方案,但是并不是说我们暗示应该将它们结合使用。这应该是具体问题具体分析的。
将 Qt 的信号槽系统与 Boost.Signals 结合使用,最大的障碍是,Qt 使用预处理器定义了关键字 signals,slots 以及 emit。这些可以看做是 Qt 对 C++ 语言的扩展。同时,Qt 也提供了另外一种方式,即使用宏来实现这些关键字。为了屏蔽掉这些扩展的关键字,Qt 4.1 的 pro 文件引入了 no_keywords 选项,以便使用标准 C++ 的方式,方便 Qt 与其他 C++ 同时使用。你可以通过打开 no_keywords 选项,来屏蔽掉这些关键字。下面是一个简单的实现:
# TestSignals.pro (platform independent project file, input to qmake)
# showing how to mix Qt Signals and Slots with Boost.Signals
#
# Things you'll have in your .pro when you try this...
#
CONFIG += no_keywords
# so Qt won't #define any non-all-caps `keywords'
INCLUDEPATH += . /usr/local/include/boost-1_33_1/
# so we can #include
macx:LIBS += /usr/local/lib/libboost_signals-1_33_1.a
# ...and we need to link with the Boost.Signals library.
# This is where it lives on my Mac,
# other platforms would have to add a line here
#
# Things specific to my demo
#
CONFIG -= app_bundle
# so I'll build a command-line tool instead of a Mac OS X app bundle
HEADERS += Sender.h Receiver.h
SOURCES += Receiver.cpp main.cpp
请注意,我们已经在 pro 文件中打开了 no_keywords 选项,那么,类似 signals 这样的关键字已经不起作用了。所以,我们必须将这些关键字修改成相应的宏的版本。例如,我们需要将 signals 改为 Q_SIGNALS,将 slots 改为 Q_SLOTS 等等。请看下面的代码:
// Sender.h
#include
#include
#include
class Sender : public QObject
{
Q_OBJECT
Q_SIGNALS: // a Qt signal
void qtSignal( const std::string& );
// connect with
// QObject::connect(sender, SIGNAL(qtSignal(const std::string&)), ...
public: // a Boost signal for the same signature
boost::signal< void ( const std::string& ) > boostSignal;
// connect with
// sender->boostSignal.connect(...
public: // an interface to make Sender emit its signals
void sendBoostSignal( const std::string& message ) {
boostSignal(message);
}
void sendQtSignal( const std::string& message ) {
qtSignal(message);
}
};
现在我们有了一个发送者,下面来看看接收者:
// Receiver.h
#include
#include
class Receiver : public QObject
{
Q_OBJECT
public Q_SLOTS:
void qtSlot( const std::string& message );
// a Qt slot is a specially marked member function
// a Boost slot is any callable signature
};
// Receiver.cpp
#include "Receiver.h"
#include
void Receiver::qtSlot( const std::string& message )
{
std::cout << message << std::endl;
}
下面,我们来测试一下:
// main.cpp
#include
#include "Sender.h"
#include "Receiver.h"
int main( int /*argc*/, char* /*argv*/[] )
{
Sender* sender = new Sender;
Receiver* receiver = new Receiver;
// connect the boost style signal
sender->boostSignal.connect(boost::bind(&Receiver::qtSlot, receiver, _1));
// connect the qt style signal
QObject::connect(sender, SIGNAL(qtSignal(const std::string&)),
receiver, SLOT(qtSlot(const std::string&)));
sender->sendBoostSignal("Boost says 'Hello, World!'");
sender->sendQtSignal("Qt says 'Hello, World!'");
return 0;
}
这段代码将会有类似下面的输出:
[506]TestSignals$ ./TestSignals
Boost says 'Hello, World!'
Qt says 'Hello, World!'
我们可以看到,这两种实现的不同之处在于,Boost.Signals 的信号,boostSignal,是 public 的,任何对象都可以直接发出这个信号。也就是说,我们可以使用如下的代码:
sender->boostSignal("Boost says 'Hello, World!', directly");
从而绕过我们设置的 sendBoostSignal() 这个触发函数。另外,我们可以看到,boostSignal 完全可以是一个全局对象,这样,任何对象都可以使用这个信号。而对于 Qt 来说,signal 必须是一个成员变量,在这里,只有 Sender 可以使用我们定义的信号。
这个例子虽然简单,然而已经很清楚地为我们展示了,如何通过 Qt 发出信号来获取 Boost 的行为。在这里,我们使用一个公共的 sendQtSignal() 函数发出 Qt 的信号。然而, 为了从 Boost 的信号获取 Qt 的行为,我们需要多做一些工作:隐藏信号,但是需要提供获取连接的函数。这样看上去有些麻烦:
class Sender : public QObject
{
// just the changes...
private:
// our new public connect function will be much easier to understand
// if we simplify some of the types
typedef boost::signal< void ( const std::string& ) > signal_type;
typedef signal_type::slot_type slot_type;
signal_type boostSignal;
// our signal object is now hidden
public:
boost::signals::connection
connectBoostSignal( const slot_type& slot,
boost::signals::connect_position pos = boost::signals::at_back ) {
return boostSignal.connect(slot, pos);
}
};
应该说,这样的实现相当丑陋。实际上,我们将 Boost 的信号与连接分割开了。我们希望能够有如下的实现:
// WARNING: no such thing as a connect_proxy
class Sender
{
public:
connect_proxy< boost::signal< void ( const std::string& ) > >
someSignal() {
return someSignal_;
// ...automatically wrapped in the proxy
}
private:
boost::signal< void ( const std::string& ) > someSignal_;
};
sender->someSignal().connect(someSlot);
注意,这只是我的希望,并没有做出实现。如果你有兴趣,不妨尝试一下。
总结
前面啰嗦了这么多,现在总结一下。
信号和槽的机制实际上是观察者模式的一种变形。它是面向组件编程的一种很强大的工具。现在,信号槽机制已经成为计算机科学的一种术语,也有很多种不同的实现。
Qt 信号槽是 Qt 整个架构的基础之一,因此它同 Qt 提供的组件、线程、反射机制、脚本、元对象机制以及可视化 IDE 等等紧密地集成在一起。Qt 的信号是对象的成员函数,所以,只有拥有信号的对象才能发出信号。Qt 的组件和连接可以由非代码形式的资源文件给出,并且能够在运行时动态建立这种连接。Qt 的信号槽实现建立在 Qt 元对象机制之上。Qt 元对象机制由 Qt 提供的 moc 工具实现。moc 也就是元对象编译器,它能够将用户指定的具有 Q_OBJECT 宏的类进行一定程度的预处理,给这个增加元对象能力。
Boost.Signals 是具有静态的类型安全检查的,基于模板的信号槽系统的实现。所有的信号都是模板类 boost::signal 的一个特化;所有的槽函数都具有相匹配的可调用的签名。Boost.Signals 是独立的,不需要内省、元对象系统,或者其他外部工具的支持。然而,Boost.Signals 没有从资源文件动态建立连接的能力。
这两种实现都非常漂亮,并且都具有工业强度。将它们结合在一起使用也不是不可能的,Qt 4.1 即提供了这种可能性。
任何基于 Qt GUI 的系统都会自然而然的使用信号槽。你可以从中获取很大的好处。任何大型的系统,如果希望能够降低组件之间的耦合程度,都应该借鉴这种思想。正如其他的机制 和技术一样,最重要的是把握一个度。在正确的地方使用信号槽,可以让你的系统更易于理解、更灵活、高度可重用,并且你的工作也会完成得更快。
本文出自 “豆子空间” 博客,请务必保留此出处http://devbean.blog.51cto.com/448512/428364
chinaunix网友2010-12-01 15:24:59
很好的, 收藏了 推荐一个博客,提供很多免费软件编程电子书下载: http://free-ebooks.appspot.com