深入理解信号槽机制(一)(大局观,讲的不错)

这篇文章来自于 A Deeper Look at Signals and Slots,Scott Collins 2005.12.19。需要说明的是,我们这里所说的“信号槽”不仅仅是指 Qt 库里面的信号槽,而是站在一个全局的高度,从系统的角度来理解信号槽。所以在这篇文章中,Qt 信号槽仅仅作为一种实现来介绍,我们还将介绍另外一种信号槽的实现——boost::signal。因此,当你在文章中看到一些信号的名字时,或许仅仅是为了描述方便而杜撰的,实际并没有这个信号。

什么是信号槽?

这个问题我们可以从两个角度来回答,一个简短一些,另外一个则长些。

让我们先用最简洁的语言来回答这个问题——什么是信号槽?

  • 信号槽是观察者模式的一种实现,或者说是一种升华;
  • 一个信号就是一个能够被观察的事件,或者至少是事件已经发生的一种通知;
  • 一个槽就是一个观察者,通常就是在被观察的对象发生改变的时候——也可以说是信号发出的时候——被调用的函数;
  • 你可以将信号和槽连接起来,形成一种观察者-被观察者的关系;
  • 当事件或者状态发生改变的时候,信号就会被发出;同时,信号发出者有义务调用所有注册的对这个事件(信号)感兴趣的函数(槽)。

信号和槽是多对多的关系。一个信号可以连接多个槽,而一个槽也可以监听多个信号。

信号可以有附加信息。例如,窗口关闭的时候可能发出 windowClosing 信号,而这个信号就可以包含着窗口的句柄,用来表明究竟是哪个窗口发出这个信号;一个滑块在滑动时可能发出一个信号,而这个信号包含滑块的具体位置,或者新的值等等。我们可以把信号槽理解成函数签名。信号只能同具有相同签名的槽连接起来。你可以把信号看成是底层事件的一个形象的名字。比如这个 windowClosing 信号,我们就知道这是窗口关闭事件发生时会发出的。

信号槽实际是与语言无关的,有很多方法都可以实现信号槽,不同的实现机制会导致信号槽差别很大。信号槽这一术语最初来自 Trolltech 公司的 Qt 库(现在已经被 Nokia 收购)。1994年,Qt 的第一个版本发布,为我们带来了信号槽的概念。这一概念立刻引起计算机科学界的注意,提出了多种不同的实现。如今,信号槽依然是 Qt 库的核心之一,其他许多库也提供了类似的实现,甚至出现了一些专门提供这一机制的工具库。

简单了解信号槽之后,我们再来从另外一个角度回答这个问题:什么是信号槽?它们从何而来?

前面我们已经了解了信号槽相关的概念。下面我们将从更细致的角度来探讨,信号槽机制是怎样一步步发展的,以及怎样在你自己的代码中使用它们。

程序设计中很重要的一部分是组件交互:系统的一部分需要告诉另一部分去完成一些操作。让我们从一个简单的例子开始:

// C++
class Button
{
public:
    void clicked(); // something that happens: Buttons may be clicked
};
class Page
{
public:
    void reload(); // ...which I might want to do when a Button is clicked
};

换句话说,Page 类知道如何重新载入页面(reload),Button 有一个动作是点击(click)。假设我们有一个函数返回当前页面 currentPage(),那么,当 button 被点击的时候,当前页面应该被重新载入。

// C++ --- making the connection directly
void Button::clicked()
{
    currentPage()->reload(); // Buttons know exactly what to do when clicked
}

这看起来并不很好。因为 Button 这个类名似乎暗示了这是一个可重用的类,但是这个类的点击操作却同 Page 紧紧地耦合在一起了。这使得只要 button 一被点击,必定调用 currentPage() 的 reload() 函数。这根本不能被重用,或许把它改名叫 PageReloadButton 更好一些。

实际上,不得不说,这确实是一种实现方式。如果 Button::click() 这个函数是 virtual 的,那么你完全可以写一个新类去继承这个 Button:

// C++ --- connecting to different actions by specializing
class Button
{
public:
    virtual void clicked() = 0; // Buttons have no idea what to do when clicked
};

class PageReloadButton : public Button
{
public:
    virtual void clicked() {
        currentPage()->reload();    // ...specialize Button to connect it to a specific action
    }
};

好了,现在 Button 可以被重用了。但是这并不是一个很好的解决方案。

引入回调

让我们停下来,回想一下在只有 C 的时代,我们该如何解决这个问题。如果只有 C,就不存在 virtual 这种东西。重用有很多种方式,但是由于没有了类的帮助,我们采用另外的解决方案:函数指针。

/* C --- connecting to different actions via function pointers */
void reloadPage_action( void* ) /* one possible action when a Button is clicked */
{
    reloadPage(currentPage());
}

void loadPage_action( void* url ) /* another possible action when a Button is clicked */
{
    loadPage(currentPage(), (char*)url);
}

struct Button {
    /* ...now I keep a (changeable) pointer to the function to be called */
    void (*actionFunc_)();
    void* actionFuncData_;
};

void buttonClicked( Button* button )
{
    /* call the attached function, whatever it might be */
    if ( button && button->actionFunc_ )
        (*button->actionFunc_)(button->actionFuncData_);
}

这就是通常所说的“回调”。buttonClicked() 函数在编译期并不知道要调用哪一个函数。被调用的函数是在运行期传进来的。这样,我们的 Button 就可以被重用了,因为我们可以在运行时将不同的函数指针传递进来,从而获得不同的点击操作。

增加类型安全

对于 C++ 或者 Java 程序员来说,总是不喜欢这么做。因为这不是类型安全的(注意 url 有一步强制类型转换)。

我们为什么需要类型安全呢?一个对象的类型其实暗示了你将如何使用这个对象。有了明确的对象类型,你就可以让编译器帮助你检查你的代码是不是被正确的使用了,如同你画了一个边界,告诉编译器说,如果有人越界,就要报错。然而,如果没有类型安全,你就丢失了这种优势,编译器也就不能帮助你完成这种维护。这就如同你开车一样。只要你的速度足够,你就可以让你的汽车飞起来,但是,一般来说,这种速度就会提醒你,这太不安全了。同时还会有一些装置,比如雷达之类,也会时时帮你检查这种情况。这就如同编译器帮我们做的那样,是我们出浴一种安全使用的范围内。

回过来再看看我们的代码。使用 C 不是类型安全的,但是使用 C++,我们可以把回调的函数指针和数据放在一个类里面,从而获得类型安全的优势。例如:

// re-usable actions, C++ style (callback objects)
class AbstractAction
{
public:
    virtual void execute() = 0; // sub-classes re-implement this to actually do something
};

class Button
{
    // ...now I keep a (changeable) pointer to the action to be executed
    AbstractAction* action_;
};

void Button::clicked()
{
    // execute the attached action, whatever it may be
    if ( action_ )
        action_->execute();
}

class PageReloadAction : public AbstractAction
    // one possible action when a Button is clicked
{
public:
    virtual void execute() {
        currentPage()->reload();
    }
};
class PageLoadAction : public AbstractAction
    // another possible action when a Button is clicked
{
public:
    // ...
    virtual void execute() {
        currentPage()->load(url_);
    }
private:
    std::string url_;
};

好了!我们的 Button 已经可以很方便的重用了,并且也是类型安全的,再也没有了强制类型转换。这种实现已经可以解决系统中遇到的绝大部分问题了。似乎现在的解决方案同前面的类似,都是继承了一个类。只不过现在我们对动作进行了抽象,而之前是对 Button 进行的抽象。这很像前面 C 的实现,我们将不同的动作和 Button 关联起来。现在,我们一步步找到一种比较令人满意的方法。

http://blog.csdn.net/devbean/article/details/5998558

时间: 2024-10-08 17:50:15

深入理解信号槽机制(一)(大局观,讲的不错)的相关文章

[转]深入理解信号槽机制

原文不可考 来源链接http://blog.csdn.net/liuuze5/article/details/53523463 深入理解信号槽(一) 这篇文章来自于 A Deeper Look at Signals and Slots,Scott Collins 2005.12.19.需要说明的是,我们这里所说的"信号槽"不仅仅是指 Qt 库里面的信号槽,而是站在一个全局的高度,从系统的角度来理解信号槽.所以在这篇文章中,Qt 信号槽仅仅作为一种实现来介绍,我们还将介绍另外一种信号槽的

从零开始实现信号槽机制:一

我们从一个具体的问题入手: "现在有一堆按钮,以及一堆电器,按钮对它需要控制的对象一无所知,电器也不知道它们开关的具体类型,它们之间的关系可能是一对多,也可能是多对一,并且需要支持动态添加和删除,应该如何设计这个结构?" 这里有个形象的图: 为了实现组件间的控制,我们很容易想到"回调函数",对于C++开发者,我们肯定不希望一个类自身的处理函数存在于类外,但是类成员函数中被自动添加的隐形this形参造成了函数指针调用的不匹配,于是我们想到了使用static成员函数:

非Qt工程使用Qt的信号槽机制

非Qt工程,使用Qt的信号槽机制,蛋疼不?反正我现在就是要做这样一件蛋疼的事. 要使用Qt的信号槽机制,下面是从Qt Assist里面关于 signal & slots 的一句介绍: All classes that contain signals or slots must mention Q_OBJECT at the top of their declaration. They must also derive (directly or indirectly) from QObject.

Qt开发之信号槽机制

一.信号槽机制原理 1.如何声明信号槽 Qt头文件中一段的简化版: class Example: public QObject { Q_OBJECT signals: void customSignal(); void customSignal(int i) public slots: void customSlot(); void customSlot(int i); }; 2.宏与MOC源对象 摘录代码: // qobjectdefs.h i. … ii. #define slots iii

Qt高级——Qt信号槽机制源码解析

Qt高级--Qt信号槽机制源码解析 基于Qt4.8.6版本 一.信号槽机制的原理 1.信号槽简介 信号槽是观察者模式的一种实现,特性如下:A.一个信号就是一个能够被观察的事件,或者至少是事件已经发生的一种通知:B.一个槽就是一个观察者,通常就是在被观察的对象发生改变的时候--也可以说是信号发出的时候--被调用的函数:C.信号与槽的连接,形成一种观察者-被观察者的关系:D.当事件或者状态发生改变的时候,信号就会被发出:同时,信号发出者有义务调用所有注册的对这个事件(信号)感兴趣的函数(槽).信号和

PyQt5快速入门(二)PyQt5信号槽机制

PyQt5快速入门(二)PyQt5信号槽机制 一.信号槽机制简介 1.信号槽简介 信号槽是Qt的核心机制,也是PyQt编程中对象进行通信的机制.在Qt中,QObject对象和PyQt中所有继承自QWidget的控件都支持信号槽机制.当信号发射时,连接的槽函数会自动执行.在PyQt5中,信号与槽函数通过object.signal.connect()方法进行连接.信号槽特点如下:(1)一个信号可以连接多个槽(2)一个信号可以连接另一个信号(3)信号参数可以是任意Python类型(4)一个槽可以监听多

Qt信号槽机制源码解析

Qt信号槽机制源码解析 来源 https://blog.51cto.com/9291927/2070398 一.信号槽机制的原理 1.信号槽简介 信号槽是观察者模式的一种实现,特性如下:A.一个信号就是一个能够被观察的事件,或者至少是事件已经发生的一种通知:B.一个槽就是一个观察者,通常就是在被观察的对象发生改变的时候——也可以说是信号发出的时候——被调用的函数:C.信号与槽的连接,形成一种观察者-被观察者的关系:D.当事件或者状态发生改变的时候,信号就会被发出:同时,信号发出者有义务调用所有注

信号槽机制的心得

使用信号和槽机制,要注意以下问题: 1.信号和槽的机制是非常有效的,但是它不像“真正的”回调那样快.信号和槽稍微有些慢,这是因为它们所提供的灵活性.但这种损失相对来说是比较小的.但要追求高效率的话,比如在实时系统中就要尽量少用这种机制. 2.信号和槽机制与普通函数的调用一样,如果使用不当的话,在程序执行时有可能形成死循环,所以,在定义槽函数时一定要注意避免间接形成无限循环,即在槽中再次发射所接收到的同样的信号. 3.如果一个信号和多个槽相关联的话,那当这个信号被发射时,与之相关联的槽的执行顺序将

PyQt5信号-槽机制

signal -> emit -> slot signal.connect(slot) signal.disconnect(slot) 信号 (singal) 可以连接无数多个槽 (slot),或者没有连接槽也没有问题,信号也可以连接其他的信号. 连接的基本语句形式如下: who.singal.connect(slot) 信号是 QObject 的一个属性.只有通过 connect方法连接起来,信号-槽机制就建立起来了.类似的信号还有 disconnect 方法和emit 方法. discon