30

我反复看到人们在没有调用插槽时遇到问题。我想收集一些最常见的原因。所以也许我可以帮助人们并避免很多多余的问题。

信号/插槽连接不起作用的原因是什么?如何避免此类问题?

4

3 回答 3

50

有一些规则可以让信号和插槽的使用变得更轻松,并涵盖了连接缺陷的最常见原因。如果我忘记了什么,请告诉我。

1)检查调试控制台输出:

当发生执行错误时,调试输出可以显示原因。

2)使用信号和槽的完整签名:

代替

connect(that, SIGNAL(mySignal), this, SLOT(mySlot));

connect(that, SIGNAL(mySignal(int)), this, SLOT(mySlot(int)));

并检查您的拼写和大小写。

3)使用现有的重载:

仔细检查您是否使用了所需的信号和插槽重载,以及您使用的重载是否确实存在。

4)您的信号和插槽必须兼容:

这尤其意味着参数必须是相同的类型(允许引用)并且具有相同的顺序。

编译时语法也需要相同数量的参数。旧的运行时语法允许将信号连接到具有较少参数的插槽。

5) 始终检查 connect 方法的返回值(程序员永远不要忽略返回值):

代替

connect(that, SIGNAL(mySignal(int)), this, SLOT(mySlot(int)));

总是使用类似的东西

bool success = connect(that, SIGNAL(mySignal(int)), this, SLOT(mySlot(int)));
Q_ASSERT(success);

或者,如果您喜欢抛出异常或实施完整的错误处理。您也可以使用这样的宏:

#ifndef QT_NO_DEBUG
#define CHECK_TRUE(instruction) Q_ASSERT(instruction)
#else
#define CHECK_TRUE(instruction) (instruction)
#endif 

CHECK_TRUE(connect(that, SIGNAL(mySignal(int)), this, SLOT(mySlot(int))));

6)您需要一个用于排队连接的事件循环:

即,当您连接不同线程拥有的两个对象的信号/插槽时(所谓的排队连接),您需要exec();在插槽的线程中调用!

事件循环也需要实际服务。每当插槽的线程卡在某种繁忙的循环中时,排队的连接都不会执行!

7)您需要为排队连接注册自定义类型:

因此,在排队连接中使用自定义类型时,您必须为此目的注册它们。

首先使用以下宏声明类型:

Q_DECLARE_METATYPE(MyType)

然后使用以下调用之一:

qRegisterMetaType<MyTypedefType>("MyTypedefType"); // For typedef defined types
qRegisterMetaType<MyType>(); // For other types

8) 比旧的运行时检查语法更喜欢新的编译时语法:

代替

connect(that, SIGNAL(mySignal(int)), this, SLOT(mySlot(int)));

使用这个语法

connect(that, &ThatObject::mySignal, this, &ThisObject::mySlot));

它在编译时检查信号和槽,甚至不需要目标是实际的槽。

如果您的信号过载,请使用以下语法:

connect(that, static_cast<void (ThatObject::*)(int)> &ThatObject::mySignal), this, &ThisObject::mySlot); // <Qt5.7
connect(that, qOverload<int>::of(&ThatObject::mySignal), this, &ThisObject::mySlot); // >=Qt5.7 & C++11
connect(that, qOverload<int>(&ThatObject::mySignal), this, &ThisObject::mySlot); // >=Qt5.7 & C++14

从 Qt5.14 开始,不推荐使用重载信号。禁用已弃用的 Qt 功能以摆脱上述恶作剧。

也不要为该语法混合 const/非常量信号/槽(通常信号和槽将是非常量)。

9) 你的类需要一个 Q_OBJECT 宏:

在使用“信号”和“插槽”规范的类中,您需要添加一个 Q_OBJECT 宏,如下所示:

class SomeClass
{
   Q_OBJECT

signals:
   void MySignal(int x);
};

class SomeMoreClass
{
   Q_OBJECT

public slots:
   void MySlot(int x);
};

此宏向类添加必要的元信息。

10) 你的对象必须是活的:

一旦发送者对象或接收者对象被销毁,Qt 就会自动丢弃连接。

如果未发出信号:发送者对象是否仍然存在?如果未调用插槽:接收器对象是否仍然存在?

要检查两个对象的生命周期,请在构造函数/析构函数中使用调试器断点或某些 qDebug() 输出。

11)它仍然不起作用:

要对您的连接进行非常快速和肮脏的检查,您可以使用一些虚拟参数自行发出信号,并查看它是否被调用:

connect(that, SIGNAL(mySignal(int)), this, SLOT(mySlot(int)));
emit that->mySignal(0); // Ugly, don't forget to remove it immediately

最后,当然有可能根本没有发出信号。如果您遵循上述规则,那么您的程序逻辑可能有问题。阅读文档。使用调试器。如果现在有其他方法,请在 stackoverflow 询问。

于 2014-10-17T09:37:48.737 回答
3

在我的实践中,我遇到过在接收信号的对象中错误地覆盖 eventFilter 的情况。一些新手程序员忘记在函数结束时返回“false”。因此不允许 MetaCall 事件传递给接收对象。在这种情况下,接收对象不处理信号。

于 2019-04-25T04:08:44.697 回答
1

简短的回答

你(几乎)不必再担心这个了。始终使用 QMetaMethod/Pointer 的成员原型connect,因为如果信号和槽不兼容,它将在编译时失败。

connect(sourceObject, &SourceClass::signal, destObject, &DestClass::slot);

sourceObject如果ordestObject为空(这是意料之中的),这个原型只会在运行时失败。但是编译时会出现参数不兼容

只有极少数情况需要旧的SIGNAL/SLOT基于文字的语法,所以这应该是你最后的手段。

兼容性

如果满足以下条件,则签名兼容:

  • 您正在将信号连接到插槽或信号
  • 目标信号/槽与源信号具有相同数量更少的参数
  • 如果使用,源信号的参数可以隐式转换为目标信号/槽中的相应参数(按顺序匹配)
例子
  • 好的- signalA(int, std::string)=>signalC(int, std::string)
    • 请注意,我们正在连接到一个信号
  • 好的- signalA(int, std::string)=>slotB(int, std::string)
  • 好的- signalA(int, std::string)=>slotB(int)
    • 忽略字符串参数
  • 好的- signalA(int, std::string)=>slotB()
    • 忽略所有参数
  • 好的- signalA(int, const char*)=>slotB(int, QString)
    • 隐式转换为QString(const char*)
  • 失败- signalA(int, std::string)=>slotB(std::string)
    • int不能隐式转换为std::string
  • 失败- signalA(int, std::string)=>slotB(std::string, int)
    • 错误的顺序
  • 失败- signalA(int, std::string)=>slotB(int, std::string, int)
    • 右边的论点太多
于 2019-03-24T00:28:48.220 回答