是否有一种方法可以检查Qt中的QObject指针是否仍然有效

Is there a way to check if a QObject-pointer is still valid in Qt?

本文关键字:是否 中的 Qt QObject 指针 有效 检查 方法 一种      更新时间:2023-10-16

我有一个场景,其中匿名QObject通过发出信号来启动异步操作。接收槽存储QObject指针,并稍后设置该对象的属性。与此同时,这个物体可能不见了。

那么,有没有一种安全的方法来检查这个指针是否仍然有效?

p.S.:我知道QObject::destroyed信号,我可以将其连接到应该调用该指针的setProperty的对象。但我想知道,这是否更容易。

这是一个很好的问题,但这是个错误的问题。

有没有办法检查指针是否有效?对QPointer是专门为实现这一点而设计的。

但是,如果对象位于另一个线程中,那么这个问题的答案是无用!你只知道它在一个时间点是否有效-答案不是立即有效的。

在没有其他机制的情况下,将QPointer保持在不同线程中的对象上是没有用的——这对您没有帮助为什么?看看这个场景:

             Thread A                    Thread B
1. QPointer returns a non-zero pointer
2.                                     deletes the object
3. Use the now-dangling pointer

我知道QObject::destroyed信号,我可以将其连接到应该调用该指针的setProperty的对象。但我想知道,这是否更容易。

无论是在线程内还是跨线程边界,使用排队连接发送destroyed信号时都是无用的。它们应该在一个线程中使用,使用直接连接

当目标线程的事件循环接收到slot调用时,发起对象早就不见了。更糟糕的是,在单线程应用程序中,总是。问题的原因与QPointer相同:destroyed信号表示对象不再有效,但这并不意味着它在收到信号之前是有效的,除非您使用的是直接连接(并且在同一线程中(或阻塞排队连接

使用阻塞排队连接,请求对象的线程将阻塞,直到异步线程完成对对象删除的反应。虽然这确实"有效",但它强制两个线程在具有稀疏可用性的资源上同步,该资源是异步线程事件循环的前端。是的,这就是你所竞争的——队列中的一个位置可以任意长。虽然这对于调试来说可能是可以的,但它在生产代码中没有位置,除非可以阻止任何一个线程进行同步。

您正努力解决这样一个事实,即在线程之间传递QObject指针,并且从接收线程的角度来看,对象的生存期是不受控制的。这是你的问题。您可以通过而不是传递原始对象指针来解决所有问题。相反,您可以传递一个共享的智能指针,或者使用信号槽连接:只要连接的任一端被破坏,这些连接就会消失。这就是你想要的。

事实上,Qt自己的设计模式暗示了这一点。QNetworkReply之所以是QObject,不仅是因为它是QIODevice,还因为它必须支持跨线程边界的已完成请求的直接指示。鉴于正在处理大量请求,连接到QNetworkAccessManager::finished(QNetworkReply*)可能是一种过早的悲观情绪。您的对象可能会收到大量回复的通知,但它实际上只对其中一个或极少数回复感兴趣。因此,必须有一种方法直接通知请求者其唯一的请求已经完成——这就是QNetworkReply::finished的作用。

因此,一种简单的方法是使Request成为具有done信号的QObject。准备好请求后,将请求对象连接到该信号。您也可以连接一个函子,但要确保函子在请求对象的上下文中执行:

// CORRECT
connect(request, &Request::done, requester, [...](...){...});
// WRONG
connect(request, &Request::done, [...](...){...});

下面展示了如何将其组合在一起。请求的生存期是通过使用共享(引用计数(智能指针来管理的。这使生活变得相当轻松。我们检查main返回时是否不存在请求。

#include <QtCore>
class Request;
typedef QSharedPointer<Request> RequestPtr;
class Request : public QObject {
   Q_OBJECT
public:
   static QAtomicInt m_count;
   Request() { m_count.ref(); }
   ~Request() { m_count.deref(); }
   int taxIncrease;
   Q_SIGNAL void done(RequestPtr);
};
Q_DECLARE_METATYPE(RequestPtr)
QAtomicInt Request::m_count(0);
class Requester : public QObject {
   Q_OBJECT
   Q_PROPERTY (int catTax READ catTax WRITE setCatTax NOTIFY catTaxChanged)
   int m_catTax;
public:
   Requester(QObject * parent = 0) : QObject(parent), m_catTax(0) {}
   Q_SLOT int catTax() const { return m_catTax; }
   Q_SLOT void setCatTax(int t) {
      if (t != m_catTax) {
         m_catTax = t;
         emit catTaxChanged(t);
      }
   }
   Q_SIGNAL void catTaxChanged(int);
   Q_SIGNAL void hasRequest(RequestPtr);
   void sendNewRequest() {
      RequestPtr req(new Request);
      req->taxIncrease = 5;
      connect(req.data(), &Request::done, this, [this, req]{
         setCatTax(catTax() + req->taxIncrease);
         qDebug() << objectName() << "has cat tax" << catTax();
         QCoreApplication::quit();
      });
      emit hasRequest(req);
   }
};
class Processor : public QObject {
   Q_OBJECT
public:
   Q_SLOT void process(RequestPtr req) {
      QThread::msleep(50); // Pretend to do some work.
      req->taxIncrease --; // Figure we don't need so many cats after all...
      emit req->done(req);
      emit done(req);
   }
   Q_SIGNAL void done(RequestPtr);
};
struct Thread : public QThread { ~Thread() { quit(); wait(); } };
int main(int argc, char ** argv) {
   struct C { ~C() { Q_ASSERT(Request::m_count == 0); } } check;
   QCoreApplication app(argc, argv);
   qRegisterMetaType<RequestPtr>();
   Processor processor;
   Thread thread;
   processor.moveToThread(&thread);
   thread.start();
   Requester requester1;
   requester1.setObjectName("requester1");
   QObject::connect(&requester1, &Requester::hasRequest, &processor, &Processor::process);
   requester1.sendNewRequest();
   {
      Requester requester2;
      requester2.setObjectName("requester2");
      QObject::connect(&requester2, &Requester::hasRequest, &processor, &Processor::process);
      requester2.sendNewRequest();
   } // requester2 is destructed here
   return app.exec();
}
#include "main.moc"

无法检查该指针是否仍然有效。因此,这里唯一安全的方法是通知接收部分删除该QObject(在多线程的情况下:在访问对象之前,您需要检查并阻止它,以确保在检查后不会在另一个线程中删除该对象(。原因很简单:

  • 从理论上讲,删除初始对象后,系统可能会在该内存中放入另一个对象(所以指针看起来是有效的(
  • 或者,对象可能会被删除,但它的内存不会被其他东西覆盖,所以它看起来仍然有效(但事实上它是无效的(
  • 所以,并没有任何方法可以检测指针是否有效,如果您只有指针的话。你需要更多的东西

此外,在多线程的情况下,仅仅发送一个关于删除对象的信号是不安全的(或者按照您的建议使用QObject::destroyed(。为什么?因为这是可能的,事情按照这个顺序发生:

  • QObject发送消息"我将被删除">
  • QObject已删除
  • 你的接收代码使用了那个指针(这是错误和危险的(
  • 您的接收代码收到消息"我将被删除"(为时已晚(

因此,在只有一个线程的情况下,您需要QPointer。否则,您需要像QSharedPointerQWeakPointer这样的东西(它们都是线程安全的(——请参阅Kuba Ober的答案。