将 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 #includemacx: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 的系统都会自然而然的使用信号槽。你可以从中获取很大的好处。任何大型的系统,如果希望能够降低组件之间的耦合程度,都应该借鉴这种思想。正如其他的机制和技术一样,最重要的是把握一个度。在正确的地方使用信号槽,可以让你的系统更易于理解、更灵活、高度可重用,并且你的工作也会完成得更快。