深入理解信号槽(四)

来源:互联网 发布:网络大学青年训练营 编辑:程序博客网 时间:2024/05/29 13:54

将 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 <boost/someheader.hpp>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 bundleHEADERS += Sender.h Receiver.hSOURCES += Receiver.cpp main.cpp

请注意,我们已经在 pro 文件中打开了 no_keywords 选项,那么,类似 signals 这样的关键字已经不起作用了。所以,我们必须将这些关键字修改成相应的宏的版本。例如,我们需要将 signals 改为 Q_SIGNALS,将 slots 改为 Q_SLOTS 等等。请看下面的代码:

// Sender.h#include <QObject>#include <string>#include <boost/signal.hpp>class Sender : public QObject{    Q_OBJECTQ_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 <QObject>#include <string>class Receiver : public QObject{    Q_OBJECTpublic 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 <iostream>void Receiver::qtSlot( const std::string& message ){    std::cout << message << std::endl;}

下面,我们来测试一下:

// main.cpp#include <boost/bind.hpp>#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$ ./TestSignalsBoost 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 hiddenpublic:    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_proxyclass 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

原创粉丝点击