我的信号/槽连接无法工作。

35

我经常看到人们遇到槽函数没有被调用的问题。我想收集一些最常见的原因,以便帮助人们避免很多重复的问题。

信号/槽连接不起作用的原因有哪些?如何避免这些问题?

3个回答

60

有一些规则可以使信号和槽的使用更加容易,并涵盖了连接失败的最常见原因。如果我遗漏了什么,请告诉我。

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 / non-const信号/插槽(通常信号和插槽将是non-const)。

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

在使用“signals”和“slots”规范的类中,您需要添加一个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上提问。


嗯,看起来QVariant并不是排队连接机制的直接部分。然而,qRegisterMetaType()的文档中说:“调用此函数注册类型T。T必须使用Q_DECLARE_METATYPE()声明。” - Silicomancer
1
@HareenLaks:请看上面,“检查您的拼写和大小写”。 - Silicomancer
非常好的总结。谢谢。 - Tobi
提示:要检查connect(...)函数中是否存在拼写错误,您可以按住CTRL键,并将鼠标移到信号或槽函数上,然后单击左鼠标按钮。如果IDE跳转到信号或槽函数的定义,则说明拼写正确。 - Wade Wang
对于Qt4,信号是受保护的,所以&ThatObject::mySignal不起作用。希望现在没有人会掉进这个坑里。 - Louis Go
显示剩余2条评论

3

在我的实践中,我曾遇到过一些错误地覆盖了接收信号对象的事件过滤器(eventFilter)的案例。一些初学者程序员会忘记在函数结尾处返回 "false",这就使得MetaCall事件无法传递到接收对象。在这种情况下,信号不会在接收对象中被处理。


2

简短回答

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

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

如果sourceObjectdestObject为空(这是可以预料的),则此原型仅在运行时失败。但是,不兼容的参数将在编译期间显示出来。

只有在罕见情况下需要使用旧的基于文字的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)
    • 右侧参数太多

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接