是否有一种方法可以检查Qt中的QObject指针是否仍然有效
Is there a way to check if a QObject-pointer is still valid in Qt?
我有一个场景,其中匿名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
。否则,您需要像QSharedPointer
或QWeakPointer
这样的东西(它们都是线程安全的(——请参阅Kuba Ober的答案。
- 是否可以在c++中处理字符串流中的各个元素
- 是否基于数组B整数打印数组A中的整数
- 有没有办法知道Tracer是否成功地完全连接到了jaegerclientcpp中的jaeger后端服务器
- 是否可以使用 C++ 中的模板减小删除代码大小
- 如果中的多个语句是否与多个 if 相同?
- 堆栈和队列是否像C++中的数组一样传递?
- 有没有一种优雅而快速的方法来测试整数中的 1 位是否位于连续区域
- 是否可以使用基类非虚拟方法中的派生类虚拟方法?
- 是否有技术原因阻止 Java 中的 final C++ 像 const 一样严格?
- 如何检查变量是否是C++中的地图?
- 使用 CTRP 时,是否访问访问父构造函数 UB 中的子属性?
- 扩展类中的可选 vir 函数,测试它在运行时是否存在
- 是否可以在 OpenGL 中的同一调用中呈现两个具有不同索引起点的不同缓冲区?
- 我们是否需要为 C++ 中的多个函数初始化多个模板?
- C++ 11 中的锁定是否保证访问数据的新鲜度?
- 检查路径是否包含C++中的另一个路径
- 使用XOR查找O(n)-解决方案中的两个字符串是否为变位符
- 测试迭代器是否位于列表中的最后一个
- 如何知道地图中的最后一个元素是否被删除?
- 检查 TinyXML 中的元素是否存在