Qt杂谈8.浅谈Qt智能指针那些事
1 引言
在 Qt 中,智能指针是一种能够自动管理对象生命周期的指针类型。通过使用智能指针,可以避免手动释放内存和处理悬挂指针等常见的内存管理问题。Qt中常用的智能指针主要有以下几种:
- QPointer:QPointer 是 Qt 提供的空安全的智能指针,用于解决对象悬挂指针的问题。QPointer 在对象被删除后会被自动设置为 nullptr,避免访问已经无效的对象。它类似于普通指针,但提供了一些安全检查。
- QScopedPointer:QScopedPointer 是 Qt 提供的独占所有权的智能指针,用于管理动态分配的对象。QScopedPointer 在超出作用域时自动删除对象,确保对象在不再需要时被正确释放。它不能被复制,因此每次只有一个拥有对象的QScopedPointer。
- QSharedPointer:QSharedPointer 是 Qt 提供的共享引用计数的智能指针,可用于管理动态分配的对象。它通过引用计数跟踪对象的引用次数,当引用计数归零时会自动删除对象。可以通过多个 QSharedPointer 共享同一个对象,对象只会在最后一个引用者释放它时才会被删除。
- QWeakPointer:QWeakPointer 是 Qt 提供的弱引用智能指针,用于解决循环引用问题。QWeakPointer 可以引用由 QSharedPointer 管理的对象,但不会增加引用计数。QWeakPointer 需要转换成 QSharedPointer 才能访问对象,当引用计数为零时,访问会失败。
2 案例分析
2.1 QPointer
QPointer 是 Qt 框架提供的一种智能指针,用于安全地处理对象的生命周期,并在对象销毁后将指针置空,防止悬垂指针的问题。
QPointer 主要用于在持有对象的弱引用的同时能够检测对象是否已被销毁。通过 QPointer,即使持有一个对象的指针,也可以确保在对象被删除后,该指针会被自动置空。这样,在使用 QPointer 指针时,可以检查指针是否有效来判断对象是否存在。
下面是一个示例,演示 QPointer 的用法:
#include <QCoreApplication>
#include <QPointer>
#include <QDebug>
class MyClass : public QObject
{
Q_OBJECT
public:
MyClass(const QString& name) : m_name(name)
{
qDebug() << "MyClass 构造函数,名称为" << m_name;
}
~MyClass()
{
qDebug() << "MyClass 析构函数,名称为" << m_name;
}
QString getName() const { return m_name; }
private:
QString m_name;
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
MyClass *obj = new MyClass("Object");
QPointer<MyClass> myObject(obj); // 创建一个 QPointer 对象来监视 MyClass 对象
if (myObject) {
qDebug() << "对象存在,名称为" << myObject->getName();
} else {
qDebug() << "对象不存在";
}
delete obj; // 销毁对象,导致myObject为空
if (myObject) {
qDebug() << "对象存在,名称为" << myObject->getName();
} else {
qDebug() << "对象不存在";
}
return a.exec();
}
结果输出:
MyClass 构造函数,名称为 Object
对象存在,名称为 Object
MyClass 析构函数,名称为 Object
对象不存在
可以看出,通过使用 QPointer,即使持有对象的指针,也能够安全地检测对象是否有效。一旦对象被销毁,QPointer 会自动将指针置空,避免了悬垂指针的问题,这样可以在使用指向对象的指针时,更加安全和可靠。
2.2 QScopedPointer
使用 QScopedPointer 管理动态分配的内存时,它会确保在包含该 QScopedPointer 的作用域结束时,所管理的对象会被自动释放,从而避免内存泄漏的问题。下面是一个示例:
#include <QScopedPointer>
#include <QDebug>
class Resource {
public:
Resource() { qDebug() << "Resource 构造函数"; }
~Resource() { qDebug() << "Resource 析构函数"; }
};
void useResource()
{
QScopedPointer<Resource> scopedResource(new Resource());
// 执行一些操作,使用资源
qDebug() << "使用资源...";
}
int main()
{
useResource();
qDebug() << "useResource 函数执行完毕";
return 0;
}
如上,创建了一个名为 Resource 的类,并使用 QScopedPointer 在 useResource 函数内创建动态分配的 Resource 对象。当 useResource 函数结束时,QScopedPointer 的析构函数会被调用,并自动释放所管理的 Resource 对象。
输出结果如下:
Resource 构造函数
使用资源...
Resource 析构函数
useResource 函数执行完毕
从结果可以看出,Resource 的构造函数在 QScopedPointer 创建对象时被调用,而析构函数在 useResource 函数结束后被调用。这表明 QScopedPointer 在适当的时候自动释放了对象的内存,确保没有内存泄漏的问题。
2.3 QSharedPointer和QWeakPointer
2.3.1 QSharedPointer
特点:
- 用于管理动态分配的对象的所有权和生命周期。
- 当存在至少一个 QSharedPointer 指向对象时,对象的内存不会被释放。
- 当最后一个指向对象的 QSharedPointer 超出作用域时,对象的内存会被释放。
- 可通过复制 QSharedPointer 来增加对象的引用计数,确保对象在合适的时候被释放。
下面举个例子:
#include <QSharedPointer>
#include <QDebug>
class MyClass
{
public:
MyClass(int value) : m_value(value)
{
qDebug() << "MyClass 构造函数,数值为" << m_value;
}
~MyClass()
{
qDebug() << "MyClass 析构函数,数值为" << m_value;
}
void setValue(int value)
{
m_value = value;
}
int getValue() const
{
return m_value;
}
private:
int m_value;
};
int main()
{
QSharedPointer<MyClass> pointer1(new MyClass(10)); // 创建一个 QSharedPointer 智能指针,用于管理 MyClass 对象
{
QSharedPointer<MyClass> pointer2 = pointer1; // 复制构造函数,增加了 MyClass 对象的引用计数
qDebug() << "pointer1 的值为" << pointer1->getValue();
qDebug() << "pointer2 的值为" << pointer2->getValue();
pointer2->setValue(20); // 通过 pointer2 修改对象的值
qDebug() << "pointer1 的值为" << pointer1->getValue();
qDebug() << "pointer2 的值为" << pointer2->getValue();
} // pointer2 超出作用域,减少了 MyClass 对象的引用计数
qDebug() << "pointer1 的值为" << pointer1->getValue();
return 0;
}
结果输出:
MyClass 构造函数,数值为 10
pointer1 的值为 10
pointer2 的值为 10
pointer1 的值为 20
pointer2 的值为 20
pointer1 的值为 20
MyClass 析构函数,数值为 20
如上,在 main 函数中,创建了一个 QSharedPointer 智能指针 pointer1,它指向一个值为 10 的 MyClass 对象。然后,通过复制构造函数创建了另一个指针 pointer2,指向同一个 MyClass 对象。这个操作导致 MyClass 对象的引用计数加一。
在 pointer2 的作用域内,修改了 MyClass 对象的值,并观察到修改同时影响了 pointer1 和 pointer2 指向的对象。当 pointer2 超出作用域时,MyClass 对象的引用计数减一,但不会立即销毁,因为 pointer1 仍然持有它的引用。最后,当 pointer1 超出作用域时,MyClass 对象被正确地销毁。
2.3.2 QWeakPointer
特点:
- 用于解决 QSharedPointer 可能导致的循环引用问题。
- 不会增加对象的引用计数,不影响对象的生命周期。
- 可以从 QSharedPointer 或者另一个 QWeakPointer 创建,用于在需要时保持对对象的非拥有者式引用。
- 如果关联的 QSharedPointer 被释放,QWeakPointer 会自动置空,避免悬空指针问题。
这里重点说下循环引用,当两个或多个对象彼此持有对方的强引用时,就会形成循环引用。这种情况下,对象无法被正常释放,会导致内存泄漏。Qt 的 QWeakPointer 类是为了解决这个问题而引入的。
QWeakPointer 允许创建一个弱引用指向被QSharedPointer管理的对象,但不会增加该对象的引用计数。弱引用不会阻止对象的销毁,即使所有强引用都失效,对象的析构函数也能被正确调用。
下面是一个循环引用的示例:
#include <QSharedPointer>
#include <QWeakPointer>
#include <QDebug>
class ObjectB;
class ObjectA
{
public:
ObjectA(const QString& name) : m_name(name) {}
~ObjectA()
{
qDebug() << "ObjectA 析构函数,名称为" << m_name;
}
void setObjectB(const QSharedPointer<ObjectB>& objectB)
{
m_objectB = objectB;
}
private:
QString m_name;
QSharedPointer<ObjectB> m_objectB;
};
class ObjectB
{
public:
ObjectB(const QString& name) : m_name(name) {}
~ObjectB()
{
qDebug() << "ObjectB 析构函数,名称为" << m_name;
}
void setObjectA(const QSharedPointer<ObjectA>& objectA)
{
m_objectA = objectA;
}
private:
QString m_name;
QSharedPointer<ObjectA> m_objectA;
};
int main()
{
QSharedPointer<ObjectA> objectA(new ObjectA("ObjectA"));
QSharedPointer<ObjectB> objectB(new ObjectB("ObjectB"));
objectA->setObjectB(objectB);
objectB->setObjectA(objectA);
qDebug() << "程序结束";
return 0;
}
结果输出:
程序结束
如上,在 main 函数中,创建了两个 QSharedPointer,用于管理 ObjectA 和 ObjectB 对象的生命周期。然后,通过 setObjectB 和 setObjectA 函数,相互设置对方的强引用,这样就形成了循环引用,导致对象无法正常销毁,从而出现内存泄漏。
为了避免这个问题,将 m_objectB 和 m_objectA 至少一个声明为 QWeakPointer 类型,如下:
QSharedPointer<ObjectB> m_objectB -> QWeakPointer<ObjectB> m_objectB
或
QSharedPointer<ObjectA> m_objectA -> QWeakPointer<ObjectA> m_objectA
由于使用了 QWeakPointer,不会增加对象的引用计数,这样也就打破了循环引用。当 objectA 和 objectB 超出作用域时,它们的引用计数会递减,对象能够被正常销毁。修改后结果输出如下:
程序结束
ObjectB 析构函数,名称为 "ObjectB"
ObjectA 析构函数,名称为 "ObjectA"
可以看到,对象的析构函数被正确调用,大家可以自行验证下。
3 总结
Qt 框架提供了多种智能指针类,用于简化对象生命周期管理和避免悬垂指针问题,使用这些智能指针类可以更方便、安全地管理对象的生命周期,减少内存泄漏和悬垂指针问题的发生,提高代码的可靠性和性能。
每一步踏出,都是一次探索,一次成长。