c++ - 有没有办法检查 QObject 指针在 Qt 中是否仍然有效?

标签 c++ qt pointers

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

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

附言: 我知道 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

I'm aware of QObject::destroyed signal, which I could connect to the object supposed to call the setProperty of that pointer. But I wonder, if it works easier.

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

当目标线程的事件循环接收到插槽调用时,原始对象早已不复存在。更糟糕的是 - 在单线程应用程序中总是这种情况。问题原因和QPointer一样:destroyed信号表示对象不再有效,但不代表之前有效您收到信号除非您使用的是直接连接(并且在同一个线程中)或者您使用的是阻塞队列连接

使用阻塞队列连接,请求对象的线程将阻塞,直到异步线程完成对对象删除的 react 。虽然这肯定“有效”,但它迫使两个线程在可用性稀疏的资源上同步——异步线程事件循环的前端。是的,这就是您争夺的真正目标 - 可以任意长的队列中的一个位置。虽然这对于调试可能没问题,但它在生产代码中没有位置,除非可以阻止任一线程进行同步。

您正在努力解决以下事实:您在线程之间传递一个 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"

关于c++ - 有没有办法检查 QObject 指针在 Qt 中是否仍然有效?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32218208/

相关文章:

Qt5 QML,何时使用 ColumnLayout 与 Column?

c++ - QItemDelegate 和 QStyledItemDelegate 有什么区别?

c++ - 指针中的 constexpr 有区别吗

c++ - 没有运算符(operator)发现 boost read_xml 函数错误

c++ - 是否必须在 stoi(s.substr(2,3)) 之前写入 "std::"?

c++ - 从 QList 中删除一个项目会抛出错误

go - 使用指针修改 slice 元素

c - 当我尝试将结构指针传递给函数时出现问题

c++ - 您可以将引用绑定(bind)到未初始化的成员吗?

c++ - 为什么 'unspecified_bool' 对于对其包装类型具有内部转换的类失败?