我有一个场景,匿名QObject通过发出信号启动异步操作.接收槽存储QObject指针并稍后设置该对象的属性.同时,这个对象可能会消失.
那么,有没有一种安全的方法来检查这个指针是否仍然有效?
PS:我知道QObject::destroyed信号,我可以连接到应该调用该setProperty指针的对象.但我想知道,如果它更容易.
Rei*_*ica 12
这是一个很好的问题,但这是一个错误的问题.
有没有办法检查指针是否有效?是.QPointer是专门为此而设计的.
但是如果对象存在于另一个线程中,那么这个问题的答案就毫无用处了!您只知道它是否在单个时间点有效- 之后答案无效.
没有其他机制,QPointer在一个不同的线程中保持一个对象是没用的- 它对你没有帮助.为什么?看看这个场景:
Thread A Thread B
1. QPointer returns a non-zero pointer
2. deletes the object
3. Use the now-dangling pointer
Run Code Online (Sandbox Code Playgroud)
我知道QObject :: destroyed信号,我可以连接到应该调用该指针的setProperty的对象.但我想知道,如果它更容易.
的destroyed是否一个线程内,或跨越线程边界-使用队列的连接发送的信号时是无用的.它们旨在使用直接连接在一个线程中使用.
当目标线程的事件循环拾取槽调用时,原始对象早已消失.更糟糕 - 在单线程应用程序中总是如此.问题的原因与以下内容相同QPointer:destroyed信号表示对象不再有效,但并不表示在收到信号之前它是有效的,除非您使用直接连接(并且在相同的线程)或您正在使用阻塞排队连接.
使用阻塞排队连接,请求对象的线程将阻塞,直到异步线程完成对对象删除的响应.虽然这肯定"有效",但它强制两个线程在资源上与稀疏可用性同步 - 这是异步线程事件循环中的前端.是的,这实际上就是你所争夺的 - 队列中的一个点可以任意长.虽然这可能适用于调试,但它在生产代码中没有位置,除非可以阻止任一线程进行同步.
你正试图在你QObject在线程之间传递指针的事实上非常努力,并且从接收线程的角度来看,对象的生命周期是不受控制的.那是你的问题.你可以通过不传递原始对象指针来解决所有问题.相反,您可以传递共享智能指针,或使用信号槽连接:每当连接的任何一端被破坏时,这些都会消失.这就是你想要的.
事实上,Qt自己的设计模式暗示了这一点.QNetworkReply是QObject不仅因为它是一个QIODevice,而是因为它必须是支持跨线程边界成品请求的直接指示.鉴于正在处理大量请求,连接QNetworkAccessManager::finished(QNetworkReply*)可能是过早的悲观情绪.您的对象会收到可能非常大量的回复的通知,但它实际上只对其中一个或极少数感兴趣.因此,必须有一种方法可以直接通知请求者其唯一的请求已经完成 - 这就是为什么QNetworkReply::finished.
因此,一个简单的方法是使Requesta QObject成为一个done信号.准备好请求后,将请求对象连接到该信号.您还可以连接仿函数,但要确保仿函数在请求对象的上下文中执行:
// CORRECT
connect(request, &Request::done, requester, [...](...){...});
// WRONG
connect(request, &Request::done, [...](...){...});
Run Code Online (Sandbox Code Playgroud)
下面演示了它如何组合在一起.通过使用共享(引用计数)智能指针来管理请求的生命周期.这让生活变得相当容易.我们检查当时没有请求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"
Run Code Online (Sandbox Code Playgroud)
检查指针是否仍然有效是不可能的.因此,这里唯一安全的方法是通知接收部分有关删除的内容QObject(并且在多线程情况下:在访问对象之前,您需要检查并阻止它以确保在检查后不会在另一个线程中删除该对象).原因很简单:
另外,在多线程情况下发送有关删除对象的信号(或QObject::destroyed按照您的建议使用)是不安全的.为什么?因为有可能,事情按此顺序发生:
所以,如果只需要一个线程QPointer.否则你需要类似QSharedPointer或QWeakPointer(它们都是线程安全的) - 请参阅Kuba Ober的答案.