Qt6入门教程 7:信号和槽机制(原理和优缺点)

目录

一.简介

二.信号和槽

1.信号和槽机制是类型安全的

2.信号和槽是松散耦合的

三.信号(signals)

四.槽(slots)

五.信号与槽的简单模拟

六.第三方信号槽实现

七.在Qt中使用第三方的Signals和Slots

八.总结一下优点和缺点

1.优点

2.缺点


信号和槽用于对象之间的通信。信号和槽机制是Qt的核心机制,也是Qt不同于其他框架的最突出的特征。Qt的元对象系统使信号和槽成为可能。

一.简介

在GUI编程中,当我们改变一个控件,通常希望其他控件被通知到。更一般的,我们希望任意对象之间能够通信。例如,如果我们点击了“关闭”按钮,我们希望窗口的close()函数被调用。
其他工具包使用回调来实现这种通信。回调函数是一个指向函数的指针,所以如果你想要一个处理函数通知你一些事件,你可以将一个指向另一个函数(回调函数)的指针传递给处理函数。处理函数然后在适当的时候调用回调函数。但回调可能不太直观,而且在确保回调参数的类型正确性方面可能会遇到问题。

二.信号和槽

在Qt中,我们有一个回调技术的替代方案:我们使用信号和槽。当特定事件发生时发出信号。Qt的控件有许多预定义的信号,但是我们总是可以子类化控件,添加一些自定义信号。槽(slots)是响应特定信号(signals)而调用的函数。Qt的控件有许多预定义的槽,但是通常的做法是子类化控件并自定义槽函数,这样就可以处理感兴趣的信号。
下图为信号与槽的关系图:支持一对多,多对一

Qt6入门教程 7:信号和槽机制(原理和优缺点)_第1张图片

1.信号和槽机制是类型安全的

信号与槽机制要求信号和槽的参数一致,所谓一致,是参数类型一致。如果不一致,允许的情况是,信号的参数可以比槽函数的参数多,此时槽可以忽略多余参数。由于参数的一致性,所以当使用基于函子(functor-base)的信号与槽语法时,编译器可以帮助我们检测类型是否匹配(Qt5开始支持的语法);基于字符串(string-base)的信号和槽语法将在运行时检测类型不匹配(Qt4开始支持的语法)。

注:functor(函子)是C++编程语言中的一个概念,它允许将一个可调用对象(函数、函数指针、成员函数指针等)以及其相关的状态(变量、成员变量等)封装起来,形成一个对象,这个对象可以像函数一样被调用。

2.信号和槽是松散耦合的

信号和槽是松散耦合的,发出信号的类既不知道也不关心哪个槽接收信号。Qt的信号和槽机制确保,如果您将信号连接到槽,将在正确的时间使用信号的参数调用该槽。信号和槽可以采用任意数量的任何类型的参数。它们完全是类型安全的。
从QObject或其子类(例如QWidget)继承的所有类都可以包含信号和槽。当对象的状态发生变化时,它们会发出信号,它不知道或不关心是否有任何槽在接收它发出的信号,这足以实现信息封装。
槽可以用于接收信号,但它们也是正常的成员功能。就像一个对象不知道是否有任何其他对象接收到它的信号一样,槽也不知道它是否有任何信号连接到它。这确保了可以使用Qt创建真正独立的组件。
您可以将任意多个信号连接到一个槽,并且可以将信号连接到任意多个槽。甚至可以将一个信号直接连接到另一个信号。(每当发出第一个信号时,这将立即发出第二个信号。)
信号和槽共同构成了强大的组件编程机制。

三.信号(signals)

当对象内部状态以对象的客户端或用户感兴趣的某种方式发生变化时—比如点击、鼠标移动等,对象就会发出信号。信号(signals)都是共有(public)的,可以从任何地方发出,但最好只从该定义信号的类及其子类使用该信号。当信号发出时,通常采用直连方式连接槽函数,这种连接方式会立即执行槽函数,就像普通的函数调用一样。此时信号和槽机制完全独立于任何GUI事件循环。一旦所有的槽都返回,emit语句之后的代码就会执行(同步发送)。当使用队列连接时,情况略有不同;在这种情况下,emit关键字后面的代码将立即继续,槽将稍后执行(异步发送)。如果多个槽连接到一个信号,当信号发出时,槽将按照它们连接的顺序依次执行。信号是由moc(元对象编译器)自动生成的,在build目录中的moc_**文件中。信号永远不能有返回类型(即使用void)。

四.槽(slots)

槽在信号发出时被调用。槽也是C++函数,可以像普通函数一样正常调用,它们唯一的特点是可以连接信号。因为槽函数是普通的成员函数,所以当直接调用时,它们遵循普通的C++规则。与回调相比,信号和槽稍微慢一些,因为它们提供了更大的灵活性,尽管实际应用程序中的差异并不大。一般来说,emit一个连接到一些槽的信号,大约比直接调用非虚函数慢十倍。原因是在定位连接对象、安全遍历所有连接(即检查在发送过程中后续槽函数是否被销毁)等所需的开销。虽然十个非虚函数调用可能听起来很多,但它的开销比任何new或delete操作都要小得多。一旦在后续执行需要new或delete字符串、vector or list等操作,信号和槽开销只占整个函数调用开销的很小一部分。信号和槽机制的简单性和灵活性是非常值得的,用户甚至不会注意到这些开销。
注:当定义了signals或slots变量的第三方库与基于qt的应用程序一起编译时,可能会导致编译器警告和错误。要解决这个问题,请#undef有问题的预处理器符号

五.信号与槽的简单模拟

#include 
#include 
#include 
#include 
// 这里是以字符串“CaoShangPa”作为连接信号和槽的媒介
class Connections
{
public:
    // 按名称建立映射关系
    void connect(const std::string &name, const std::function &callback)
    {
        m_callbackMap[name] = callback;
    }
    // 按名称调用
    void invoke(const std::string &name)
    {
        auto it = m_callbackMap.find(name);
        if (it != m_callbackMap.end()) {
            it->second();
        }
    }
private:
    std::map> m_callbackMap;
};
 
static Connections g_connections;
 
class Signal
{
public:
    void send()
    {
        std::cout << "I am signal" << std::endl;
        // 调用名字为CaoShangPa的回调
        g_connections.invoke("CaoShangPa");
    }
};
 
class Slot
{
public:
    Slot()
    {
        // 构造函数中,建立映射关系
        g_connections.connect("CaoShangPa", std::bind(&Slot::recieve, this));
    }
 
    void recieve()
    {
        std::cout << "I am slot" << std::endl;
    }
};
 
int main(int argc, char *argv[])
{
    Signal signal;
    Slot slot;
    // 此时signal并未直接调用slot,却能打印出I am slot
    signal.send();
 
    return 0;
}

六.第三方信号槽实现

信号-槽是非常优秀的通信机制,但Qt的moc实现方式,被一些人诟病,所以他们造了新的轮子,比如:
https://woboq.com/blog/verdigris-qt-without-moc.html
http://sigslot.sourceforge.net/
https://github.com/NoAvailableAlias/nano-signal-slot
https://github.com/pbhogan/Signals

七.在Qt中使用第三方的Signals和Slots

可以将Qt与第三方信号/槽机制一起使用。你甚至可以在同一项目中使用这两种机制。只需将以下行添加到qmake项目(.pro)文件中。

CONFIG += no_keywords

它告诉Qt不要定义moc关键字signals、slot和emit,因为这些名称将由第三方库使用,例如Boost。然后,要继续使用带有no_keywords标志的Qt信号和槽,只需将源代码中Qt moc所使用的关键字替换为相应的Qt宏Q_SIGNALS(或Q_SIGNAL)、Q_SLOTS(或Q_SLOT)和Q_EMIT。
以上内容参考:Qt Assistant—>Signals & Slots

八.总结一下优点和缺点

1.优点

①类型安全。
②松散耦合。信号和槽机制减弱了Qt对象的耦合度。发送信号的Qt对象无需知道是那个对象的那个信号槽接收它发出的信号,它只需在适当的时间发送适当的信号即可。Qt可以保证了适当的槽得到了调用,即使关联的对象在运行时被删除。程序也不会奔溃。
③灵活性。一个信号可以关联多个槽,或多个信号关联同一个槽。

2.缺点

速度较慢。与回调函数相比,信号和槽机制运行速度比直接调用非虚函数慢10倍。
原因:
①需要定位接收信号的对象。
②安全地遍历所有关联槽。
③编组、解组传递参数。
④多线程的时候,信号需要排队等待。
然而,与创建对象的new操作及删除对象的delete操作相比,信号和槽的运行代价只是他们很少的一部分。信号和槽机制导致的这点性能损耗,对实时应用程序是可以忽略的。

原文链接:Qt6入门教程 7:信号和槽机制(原理和优缺点)-CSDN博客

你可能感兴趣的:(Qt6入门教程,Qt,信号,槽,signal,slot,signals,slots)