【发布时间】:2012-08-09 22:23:33
【问题描述】:
调试信号和槽可能很困难,因为调试器在发出信号时不会跳转到信号的槽。调试 Qt 信号和槽的最佳实践有哪些?
特别是
- 如何确保连接设置成功?
- 什么时候应该使用信号和槽,什么时候应该避免它们?
- 根据您的经验,最有效的调试技术是什么?
【问题讨论】:
标签: qt debugging signals-slots connection
调试信号和槽可能很困难,因为调试器在发出信号时不会跳转到信号的槽。调试 Qt 信号和槽的最佳实践有哪些?
特别是
【问题讨论】:
标签: qt debugging signals-slots connection
除了已经说过的,这里还有一些额外的技巧。
如果您使用 QTest 进行单元测试,那么您可以将 -vs 参数传递给可执行文件,所有信号都将显示在控制台中。
我查看了 QTest 的工作原理,它注册了在使用 QSignalDumper 类执行信号和槽时触发的回调。但是,此 API 不会导出,并且可能随时中断。以下是我如何使用 GCC 使用 Qt 5.10 连接 Linux 上的所有信号和插槽。
// QSignalSpyCallbackSet is defined in qt5/qtbase/src/corelib/kernel/qobject_p.h
struct QSignalSpyCallbackSet
{
typedef void (*BeginCallback)(QObject *caller, int signal_or_method_index, void **argv);
typedef void (*EndCallback)(QObject *caller, int signal_or_method_index);
BeginCallback signal_begin_callback,
slot_begin_callback;
EndCallback signal_end_callback,
slot_end_callback;
};
typedef void (*register_spy_callbacks)(const QSignalSpyCallbackSet &callback_set);
static void showObject(QObject *caller, int signal_index, const QString &msg)
{
const QMetaObject *metaObject = caller->metaObject();
QMetaMethod member = metaObject->method(signal_index);
qDebug() << msg << metaObject->className() << qPrintable(member.name());
}
static void onSignalBegin(QObject *caller, int signal_index, void **argv)
{
showObject(caller, signal_index, "onSignalBegin");
}
static void onSlotBegin(QObject *caller, int signal_index, void **argv)
{
showObject(caller, signal_index, "onSlotBegin");
}
int main(int argc, char *argv[])
{
static QSignalSpyCallbackSet set = { onSignalBegin, onSlotBegin, 0, 0 };
QLibrary qtcore("libQt5Core");
register_spy_callbacks reg = (register_spy_callbacks)qtcore.resolve("_Z32qt_register_signal_spy_callbacksRK21QSignalSpyCallbackSet");
if (reg) {
reg(set);
}
...
}
我认为 Qt 应该公开该 API,因为我们可以将它用于除调试之外的许多事情,例如监控在插槽中花费的时间、获取统计信息等。
【讨论】:
关于 #1,我将添加另一条我在上面或参考的博客文章中没有看到的信息。
来自QObject::connect()上的文档:
从发送者对象中的信号到接收者对象中的方法创建一个给定类型的连接。如果连接成功,则返回 true;否则返回 false。
我更喜欢断言我的连接的返回值以确保连接成功,尤其是因为并非所有 Qt 程序都会有控制台输出。这也导致代码更易于维护,因为它会捕获以后对信号或插槽所做的更改,并强制进行更改的程序员也更新连接。
【讨论】:
不久前有一篇名为20 ways to debug Qt signals and slots的博客文章
它解决了我认为您的问题中的 #1 和 #3。
对于#2,我不认为使用或不使用信号/插槽真的有一个硬性和快速的理由,因为它们是 GUI 框架的一个非常核心的概念。信号是将一个组件的知识与另一个组件解耦的完美方式,允许您设计可重用的小部件,这些小部件只需声明状态更改或通知。通过发出主线程可以看到的信号,这也是从非 GUI 线程循环传达 GUI 更改的一种非常好的方法。
有时,您真正想要的不是信号/槽,而是使用事件,例如当父小部件应成为许多子小部件的事件过滤器时。孩子们仍然不需要知道父母,父母会得到更直接的事件而不是信号连接。
在事件的同一主题上,有时您真正想要的是从孩子 -> 父母 -> 祖父母 -> 等处冒泡事件。信号在这里就没那么有意义了,因为它们并不意味着确定提议的事件是否应该导致动作的方法(显然可以这样使用它们)。事件允许您检查当前状态,决定此小部件是否应该执行任何操作,或者以其他方式将它们向上冒泡以供其他人检查。
关于The Difference Between Signals/Slots and Events 有一个非常棒的答案。这是一个很好的sn-p:
- 你“处理”事件
- 您会“收到有关”信号发射的通知
我喜欢这句话的地方在于它描述了不同的需求案例。如果您需要处理小部件中的操作,那么您可能需要一个事件。如果您想在发生的事情时收到通知,那么您可能需要一个信号。
【讨论】:
qt_static_metacall 调用,没有简单的方法来破译发出的信号,而且根本无法知道在哪里设置了任何特定的信号槽连接.
如何确保连接设置成功?
对于每个失败的连接,您都会在应用程序的控制台输出中看到警告。
什么时候应该使用信号和槽,什么时候应该避免它们?
在我看来,只要您想在类设计中保持关注点分离,就可以使用它们。您的班级可以发出一个信号,该信号可能会或可能不会被您的班级完全未知的另一个(或多个班级)回答。这样可以降低耦合度。
根据您的经验,最有效的调试技术是什么?
除了这篇博文中所说的之外,我真的无法再添加任何内容了。 20 ways to debug Qt signals and slots
【讨论】: