调试信号和槽可能很困难,因为调试器在发出信号时不会跳转到信号的槽。调试 Qt 信号和插槽的最佳实践是什么?
尤其
- 如何确保成功建立连接?
- 什么时候应该使用信号和插槽,什么时候应该避免它们?
- 根据您的经验,最有效的调试技术是什么?
调试信号和槽可能很困难,因为调试器在发出信号时不会跳转到信号的槽。调试 Qt 信号和插槽的最佳实践是什么?
尤其
不久前写了一篇名为20 种调试 Qt 信号和插槽
的方法的博客文章,
它解决了我认为您的问题中的 #1 和 #3。
对于#2,我不认为使用或不使用信号/插槽真的有一个硬性和快速的理由,因为它们是 GUI 框架的一个非常核心的概念。信号是将一个组件的知识与另一个分离的完美方式,允许您设计可重用的小部件,这些小部件只需声明状态更改或通知。通过发出主线程可以看到的信号,这也是从非 GUI 线程循环传达 GUI 更改的一种非常好的方法。
有时您可能真正想要的是使用事件而不是信号/插槽,例如当父小部件应成为许多子小部件的事件过滤器时。孩子们仍然不需要知道父母,父母得到一个更直接的事件而不是信号连接。
在事件的同一主题上,有时您真正想要的是从孩子 -> 父母 -> 祖父母 -> 等事件中冒泡。信号在这里意义不大,因为它们不是一种确定的方式提议的事件是否应该导致行动(显然可以这样使用)。事件允许您检查当前状态,决定这个小部件是否应该做任何事情,或者以其他方式将它们向上冒泡以供其他人检查。
关于The Difference Between Signals/Slots and Events有一个非常棒的答案。这是一个很好的片段:
- 你“处理”事件
- 您“得到通知”信号发射
我喜欢这句话的地方在于它描述了不同的需求案例。如果您需要处理小部件中的操作,那么您可能需要一个事件。如果您想在发生的事情时收到通知,那么您可能需要一个信号。
除了已经说过的,这里还有一些额外的技巧。
如果您使用 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,因为我们可以将它用于除调试之外的许多事情,例如监控在插槽中花费的时间、获取统计信息等。
如何确保成功建立连接?
对于每个失败的连接,您将在应用程序的控制台输出中看到警告。
什么时候应该使用信号和插槽,什么时候应该避免它们?
在我看来,只要您想在类设计中保持关注点分离,就可以使用它们。您的班级可以发出一个信号,该信号可能会或可能不会被您班级完全未知的另一个(或多个班级)回答。这使您的耦合降低。
根据您的经验,最有效的调试技术是什么?
除了这篇博文中所说的之外,我真的无法添加任何内容。调试 Qt 信号和槽的 20 种方法
关于#1,我将添加另一条我在上面或在引用的博客文章中没有看到的信息。
从文档中QObject::connect()
:
创建从发送者对象中的信号到接收者对象中的方法的给定类型的连接。如果连接成功,则返回 true;否则返回假。
我更喜欢断言我的连接的返回值以确保连接成功,特别是因为并非所有 Qt 程序都会有控制台输出。这也导致更易于维护的代码,因为它将捕获稍后对信号或插槽所做的更改,并强制进行更改的程序员也更新连接。