Qt:Connect inside constructor - 在初始化对象之前会调用插槽吗?

Qt: Connect inside constructor - Will slot be invoked before object is initialized?

我正在学习 Qt 框架 (C++),想知道 QT 是否有任何机制来保护槽在对象完全初始化之前不被调用。

考虑Class一个构造函数:

A::A() {
    mTreeView = new QTreeView();
    ...
    connect(mTreeView, &QTreeView::customContextMenuRequested,
        this, &A::OnContextMenuRequested);
    ...
}

我担心用户可以在 A 的构造函数完成之前右键单击树视图。另一种上下文如下:

A::A() {
    mQObj = new MyQObject();
    connect(mQObj, &MyQObject::SomeEvent, this, &A::OnEvent);
}

A::InitB() { mB = new B(); }

A::OnEvent() { mB.doSomething(); }

在这里,可以在 InitB() 运行之前调用 doSomething() 方法。

我需要担心这种情况吗?如果是这样,有没有一种方法可以避免这些问题,而不必先初始化所有对象,然后返回并分别连接事件?

在大多数情况下您不必担心这种情况,因为事件是在同一个线程中传递的。您不必关心任何 "hidden multithreading" 的事情。如果您没有在 A 的构造函数中显式调用导致事件被处理的函数,那么您是安全的,并且您当前的方法、槽等的执行在下一个事件被处理之前完成。

也就是说,处理新事件并因此执行其他代码(事件处理程序、槽)的情况是:

  1. 当前事件处理程序、插槽等的执行完成(您创建的代码A)并且Qt returns进入事件循环等待下一个事件。在您的情况下,那是在完全构建 A 实例之后。
  2. 你启动一个本地事件循环(创建一个 QEventLoop 对象并调用 exec())
  3. 你打电话给QCoreApplication::processEvents()
  4. 您在 QDialog
  5. 上调用 exec()

1) 是 Qt 运行的正常模式:您启动 app.exec(),这会启动事件循环。之后的一切都由事件(用户输入、计时器、I/O)直接或间接触发。事件发生并被添加到事件循环的事件队列中。事件循环调用事件的事件处理程序。当事件处理程序完成时,事件循环选择下一个事件并为其调用处理程序。

所以一切都以有序的方式发生,一个事件接一个事件发生,除非事件处理程序之一(例如,监听按钮的 clicked() 信号的插槽)执行 2、3 或 4 中的一个。然后 Qt 处理下一个事件就地,即调用 exec() 或 processEvents() 的位置。相应地执行事件 handlers/slots。 然后 exec()/processEvents() returns。现在,如果不调用 exec()/processEvents(),所有确定性都消失了,不幸的是:用户可能做了随机的事情,事情可能被任意更改或删除(甚至 this 指针,如果用户关闭window,例如)。因此,尤其是 2) 和 3) 容易出错,通常会导致严重的头痛,所以我会尽可能避免使用它们,或者至少要意识到潜在的问题。

现在出现了您自己使用多线程的情况。由于所有实际的 UI 和相关的用户事件都在单个主线程中处理,这里的多线程通常意味着您有线程执行非 UI 工作,并且与您的 UI 线程交互在 UI 线程中的对象上调用函数,修改两个线程共享的数据,或使用跨线程 signal/slot 连接。

  • 如果您不使用 signal/slots,而是直接从辅助线程调用方法到 UI 线程或修改共享数据,通常的多线程规则适用:除非您知道自己在做什么并相应地使用同步。 Qt 的 UI 类 不是线程安全的。
  • 如果你使用 signal/slots,调用是 /queued/,即如果线程 B 发出一个信号并且你在主线程的一个槽中接收到它,槽的调用在与用户事件相同的方式:适用与用户事件相同的规则。除非您执行 2、3、4 之一,否则不会在您的事件 handler/slot returns 之前调用插槽。所以跨线程 signal/slots 连接是一种/消息传递/消息通过事件循环传递的方式。