我有一个场景,其中匿名 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/