摘要
C/C++程序员来讲,内存泄露是需要特别关注的点,但是QT提供智能指针,可以避免由于使用原生指针(忘记释放堆上内存)而带来的内存泄露。
QPointer
QPointer类是一个模板类,它提供了指向QObject的保护指针。 一个受保护的指针QPointer<T>,其行为与普通c++指针T *类似,只是当被引用的对象被销毁时,它会自动设置为0(不像普通c++指针,在这种情况下会变成“悬空指针”)。T必须是QObject的子类。 当您需要存储一个指向其他人拥有的QObject的指针时,保护指针是有用的,因此当您仍然持有对它的引用时可能会被销毁。您可以安全地测试指针的有效性。
QLabel * pLabel = new QLabel();
pLabel->setText("aa");
QLabel * pLabel2 = pLabel;//其中pLabel2就是期望拥有pLabel所指代的对象的指针
此时若是delete pLabel ,pLabel2 变成悬挂指针;
如下代码可解决该问题
QLabel * pLabel = new QLabel();
pLabel->setText("aa");
QPointer<QLabel> pLabel2 = pLabel;//其中pLabel2就是期望拥有pLabel所指代的对象的指针
Notice
当使用QPointer时,Qt 5在行为上有了轻微的改变。当在QWidget(或QWidget的子类)上使用QPointer时,以前QPointer会被QWidget析构函数清除。现在,QPointer被QObject析构函数清除(因为此时QWeakPointer对象被清除)。在QWidget析构函数销毁被跟踪小部件的子部件之前,任何跟踪小部件的qpointer都不会被清除。
调用方法
- 通过data()调用。data()是QPointer的成员变量,它返回指向的指针对象,通过指针对象对其成员变量引用,比如:pLabel.data()->setText(“aa”);
- 直接引用。我们通常要调用指针对象的成员变量或函数时,通常的写法是:变量+“.”,然后“.”会自动变成 “->”,但是QPointer却不是,这里我们写“pLabel.” 自动联想出来的是QPointer本身的成员函数,而非QLabel的成员函数,并且“.”也不会变成"->",那么如果我们要调用QLabel的成员函数,就得手动写"->",比如:pLabel->setText(“aa”);
QScopedPointer
QScopedPointer和C++中的智能指针std::unique_ptr其概念是一样的,它包装了new操作符在堆上分配的动态对象,能够保证动态创建的对象在任何时候都可以被正确地删除。但它有更严格的所有权,并且不能转让,一旦获取了对象的管理权,你就无法再从它那里取回来。也就是说,只要出了作用域,指针就会被自动删除,因为它的拷贝构造和赋值操作都是私有的,与QObject及其派生类风格相同。
使用案例
void myFunction(bool useSubClass)
{
QScopedPointer<MyClass> p(useSubClass ? new MyClass() : new MySubClass);
QScopedPointer<QIODevice> device(handsOverOwnership());
if (m_value > 3)
return;
process(device);
}
注意:因为拷贝构造和赋值操作私有的,所以不能用作容器的元素。
注意
- const 限制 C ++指针的const限定也可以用QScopedPointer表示:
//指向常量的常指针
const QWidget *const p = new QWidget();
const QScopedPointer<const QWidget> p(new QWidget());
//常指针
QWidget *const p = new QWidget();
const QScopedPointer<QWidget> p(new QWidget());
//指向常量的指针
const QWidget *p = new QWidget();
QScopedPointer<const QWidget> p(new QWidget());
- QScopedPointer作为函数返回值
上面说到,使用QScopedPointer智能指针动态创建的对象,一旦出了作用域就会 被自动释放并置空,那么如果需要函数返回值怎么办呢?
比如下面这种情况:
QLabel * createLabel()
{
QScopedPointer<QLabel> pLabel(new QLabel());
// return pLabel.data(); //invalid
return pLabel.take(); //valid
}
注意,我们在createLabel()函数中创建label对象并返回时,不能使用data(),而要使用take(); 因为 T *QScopedPointer::data() const返回指向对象的常量指针,QScopedPointer仍拥有对象所有权。 所以通过data()返回过后就被自动删除了,从而导致mian函数中的p1变成了野指针,程序崩溃。 而使用T *QScopedPointer::take()也是返回对象指针,但QScopedPointer不再拥有对象所有权,而是转移到调用这个函数的caller,同时QScopePointer对象指针置为NULL。
- reset函数 void QScopedPointer::reset(T *other = Q_NULLPTR):delete目前指向的对象,调用其析构函数,将指针指向另一个对象other,所有权转移到other。
- 处理数组
对应的还有一个指针QScopedArrayPointer,专门用于处理数组,其用法和QScopedPointer是一样的
官方简单示例: void foo() { QScopedArrayPointer i(new int[10]); i[2] = 42; ... return; // our integer array is now deleted using delete[] }
超出作用域过后会自动调用delete[]删除指针,这里就不展开描述了。
QSharedPointer
Qt智能指针QSharedPointer 与 C++中的std::shared_ptr其作用是一样的。
QSharedPointer 是一个共享指针,它与 QScopedPointer 一样包装了new操作符在堆上分配的动态对象,但它实现的是引用计数型的智能指针 ,也就是说,与QScopedPointer不同的是,QSharedPointer可以被自由地拷贝和赋值,在任意的地方共享它,所以QSharedPointer也可以用作容器元素。
所谓的计数型指针,就是说在内部QSharedPointer对拥有的内存资源进行引用计数,比如有3个QSharedPointer同时指向一个内存资源,那么就计数3,知道引用计数下降到0,那么就自动去释放内存啦。
需要注意的是:QSharedPointer 是线程安全的,因此即使有多个线程同时修改 QSharedPointer 对象也不需要加锁。虽然 QSharedPointer 是线程安全的,但是 QSharedPointer 指向的内存区域可不一定是线程安全的。所以多个线程同时修改 QSharedPointer 指向的数据时还要应该考虑加锁。
先来看一个官方示例:
static void doDeleteLater(MyObject *obj)
{
obj->deleteLater();
}
void otherFunction()
{
QSharedPointer<MyObject> obj = QSharedPointer<MyObject>(new MyObject, doDeleteLater);
// continue using obj
obj.clear(); // calls obj->deleteLater();
}
这个示例中,传入了一个函数,用于自定义删除。也可以直接使用成员函数:
QSharedPointer<MyObject> obj = QSharedPointer<MyObject>(new MyObject, &QObject::deleteLater);
QSharedPointer使用非常方便,直接和普通指针用法一样,创建后直接用,后面就不用管了。
我们都知道Qt的QObject对象树系统(父子),当在创建对象时如果指定了父对象,那么该对象就无需手动释放,会由父对象去做资源回收。目前,对象树和共享指针结合也不会出现崩溃。 ``` QSharedPointer obj = QSharedPointer(new QWidget(this)); ```QWeakPointer
前面我们说到的QSharedPointer是一个强引用类型的智能指针,而QWeakPointer是一个弱引用类型的智能指针,和C++中的weak_ptr功能是一样的。
QWeakPointer 是为配合 QSharedPointer 而引入的一种智能指针,它更像是 QSharedPointer 的一个助手(因为它不具有普通指针的行为,没有重载operator*和->)。它的最大作用在于协助 QSharedPointer 工作,像一个旁观者一样来观测资源的使用情况。
QWeakPointer不能用于直接取消引用指针,但它可用于验证指针是否已在另一个上下文中被删除。并且QWeakPointer对象只能通过QSharedPointer的赋值来创建。
需要注意的是,QWeakPointer不提供自动转换操作符来防止错误发生。即使QWeakPointer跟踪指针,也不应将其视为指针本身,因为它不能保证指向的对象保持有效。
QWeakPointer用于解决循环引用困境
循环引用,两个对象互相使用一个 QSharedPointer成员变量指向对方(你中有我,我中有你)。由于QSharedPointer是一个强引用的计数型指针,只有当引用数为0时,就会自动删除指针释放内存,但是如果循环引用,就会导致QSharedPointer指针的引用永远都不能为0,这时候就会导致内存无法释放。
所以QWeakPointer诞生了,它就是为了打破这种循环的。并且,在需要的时候变成QSharedPointer,在其他时候不干扰QSharedPointer的引用计数。它没有重载 * 和 -> 运算符,因此不可以直接通过 QWeakPointer 访问对象,典型的用法是通过 lock() 成员函数来获得 QSharedPointer,进而使用对象。
示例 首先,我们来看一个QSharedPointer循环使用的示例:
#include <QWidget>
#include <QDebug>
#include <QWeakPointer>
#include <QSharedPointer>
class Parent
{
public:
~Parent(){
qDebug() << __FUNCTION__;
}
QSharedPointer<Children> m_pChildren;
};
class Children
{
public:
~Children(){
qDebug() << __FUNCTION__;
}
QSharedPointer<Parent> m_pParent;
};
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = 0);
~Widget();
void test();
};
#include "widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
test();
}
Widget::~Widget()
{
qDebug() << __FUNCTION__;
}
void Widget::test()
{
QSharedPointer<Parent> parent(new Parent());
QSharedPointer<Children> children(new Children());
if(parent && children){
parent->m_pChildren = children;
children->m_pParent = parent;
}
}
在构造函数中调用test()函数,执行完过后应该会自动释放parent和children对象,但是由于相互引用,在退出之前,引用计数为2,退出之后引用计数还是1,所以导致不能自动释放,并且此时这两个对象再也无法访问到。运行过后发现并没有进入到Parent和Children的析构函数中。这就导致了内存泄漏。
那么该如何解决这个问题呢?
很简单,直接将上述代码中的
QSharedPointer<Children> m_pChildren;
QSharedPointer<Parent> m_pParent;
改成
QWeakPointer<Children> m_pChildren;
QWeakPointer<Parent> m_pParent;
这是因为在test()退出之前引用计数是1,函数退出之后就自动析构,这就解除了上面的循环引用。