QObject::connect 函数中的 QObject* 上下文

QObject* context in QObject::connect function

我已经阅读了 QObject::connect 的 documentation(针对 Qt 5.4),但我对重载有疑问

QMetaObject::Connection QObject::connect(const QObject * sender, PointerToMemberFunction signal, const QObject * context, Functor functor, Qt::ConnectionType type = Qt::AutoConnection)

context 参数到底是什么?它的目的是什么?它可以用于在线程中的本地事件循环中建立连接吗?

有人可以提供 how/when 的示例来使用此重载(当上下文不是 this 时)吗?

上下文对象在两种情况下使用。

自动断开连接

让我们先退一步问问自己:Qt 什么时候断开连接?

用通常的connect(sender, signal, receiver, slot)连接,有三种可能:

  1. 当有人明确调用 disconnect;
  2. sender被删除时;
  3. 删除receiver

特别是在 #2 和 #3 的情况下,Qt 这样做是有意义的(实际上,它 必须 那样做,否则你会发生资源泄漏and/or 崩溃)。


现在:当使用 connect 重载仿函数时,Qt 什么时候断开连接?

请注意,如果没有 context 参数,则只涉及一个 QObject:发送者。因此答案是:

  1. 当有人明确调用 disconnect;
  2. 删除sender时。

当然,这里没有receiver对象!所以只有发件人自动控制连接的生命周期。

现在,问题是仿函数可能会捕获一些可能变得无效的额外状态,在这种情况下希望连接自动中断。典型的情况是 lambdas:

connect(sender, &Sender::signal, 
        [&object1, &object2](Param p) 
        { 
            use(object1, object2, p);
        }
);

如果 object1object2 被删除会怎样?连接仍将存在,因此发出信号仍将调用 lambda,后者将访问已销毁的对象。这有点糟糕...


出于这个原因,当涉及到仿函数时,引入了采用 上下文对象 connect 重载。使用该过载建立的连接也将自动断开

  1. 删除 context 对象时。

您可能是对的,您会多次看到仿函数中使用的完全相同的 "main" 对象,例如

connect(button, 
        &QPushButton::clicked,
        otherWidget, 
        [otherWidget]() 
        { 
            otherWidget->doThis(); otherWidget->doThat(); 
        }
);

这只是 Qt 中的一种模式——在为子对象设置连接时,您通常将它们连接到 this 对象上的插槽,因此 this 可能是最常见的上下文。但是,一般来说,您也可能会得到类似

的结果
// manages the lifetime of the resources; they will never outlive this object
struct ResourceManager : QObject 
{
    Resource res1; // non-QObjects
    OtherResource res2;
};

ResourceManager manager;    
connect(sender, signal, manager, [&manager](){ use(manager.res1, ...); });
// or, directly capture the resources, not the handle

因此,您正在捕获 manager 的部分状态。


在最一般的情况下,当没有context对象可用时,如果lambda捕获的对象有可能在连接中存活下来,那么你必须通过弱指针捕获它们,并尝试锁定在尝试访问它们之前在 lambda 中的那些指针。

运行 特定 thread/event 循环中的仿函数

很快:当指定上下文对象时,仿函数将运行进入上下文的线程,就像使用接收者对象的正常连接一样。实际上,请注意 connect 重载采用上下文 采用连接类型(而没有上下文的重载不采用连接类型 - 连接始终是直接的)。

同样,这很有用,因为 QObject 不是可重入的或线程安全的,您必须仅在它所在的线程中使用 QObject。如果您的仿函数访问另一个线程中的对象,则它必须在该线程中执行线;将该对象指定为上下文可以解决问题。