Qt内存管理(三) Qt的智能指针

智能指针则可以在退出作用域时(不管是正常流程离开或是因异常离开)总调用delete来析构在堆上动态分配的对象。Qt常用的智能指针有QPointer,QScopedPointer,QSharedPointer。

关于这几个智能指针,网上的博客基本不是翻译Qt文档,就是翻译老外的博客,比较失望。

QPointer

QPointer属于Qt对象模型的特性,本质是一个模板类,它为QObje提供了guarded pointer。当其指向的对象(必须是QObject及其派生类)被销毁时,它会被自动置NULL,原理是其对象析构时会执行QObject的析构函数,进而执行QObjectPrivate::clearGuards(this);,这也是基于其指向对象都继承自QObject的原因。

QPointer对QMetaObject的相关操作做了简单的封装,这里的基本思想是
在QPointer构造的时候调用QMetaObject::addGuard(&o),把T的指针加入QMetaObject内的一个哈希表中,
在QPointer析构的时候调用QMetaObject::removeGuard(&o),把T的指针从哈希表中删除。

看代码:

QPointer<Test> t = new Test();      // Test类必须继承QObject
delete t;   //对象被delete之后,t会自动置NULL,而不会成为悬挂(dangling)的野指针
if(t.isNull())
    qDebug()<<"NULL";

运行后会输出NULL

实际中,QPointer用于解决这样的问题:在其他地方都用到了某个指针,在这个指针的对象被delete后,将指针置为空,那么其他地方的指针会变为野指针。也就是在C++中有这样的代码:

Test* p1 = new Test();
Test* p2 = p1;
delete p1;
p1 = NULL;
if(t2)
    qDebug()<<"t2不是NULL";
else
    qDebug()<<"t2成为NULL";

运行结果是t2不是NULL,也就是说t2成为了野指针。

有了QPointer,可以这样解决这个问题:

Test* t1 = new Test();
QPointer<Test> t2 = t1;
delete t1;
t1 = NULL;
if(t2)
    qDebug()<<"t2不是NULL";
else
    qDebug()<<"t2成为NULL";

运行结果是t2成为NULL,t2不再是野指针了。当然这里的t1最好也用QPointer,不过重点是t2。

QScopedPointer

QScopedPointer类保存了一个指针,指向在堆上分配的对象,在对象销毁时delete这个指针。从scope这个词就可以知道对象指针在出了作用域后就会被delete掉,不必手动delete。这个智能指针只能在本作用域里使用,不希望被转让。因为它的拷贝构造和赋值操作都是私有的,与QObject及其派生类风格相同。

Test* p = new Test();
QScopedPointer<Test> p2(p);
p2.data()->foo();
p2.take()->foo();
if(p2 == NULL)
    qDebug()<<"p2 is null";
else
    qDebug()<<"p2 is not null";

运行结果是:p2==NULL

T *QScopedPointer::data() const返回指向对象的常量指针,QScopedPointer仍拥有对象所有权。
T *QScopedPointer::take()也是返回对象指针,但QScopedPointer不再拥有对象所有权,而是转移到调用这个函数的caller,同时QScopePointer对象指针置为NULL。注意:如果没有使用take,p2会成为野指针。
void QScopedPointer::reset(T *other = Q_NULLPTR):delete目前指向的对象,调用其析构函数,将指针指向另一个对象other,所有权转移到other。

以上代码仅仅是用于处理new的情况,不能用于malloc和new []数组。

经常用于这样的代码风格:

class MyPrivateClass; // forward declare MyPrivateClass
class MyClass
{
private:
  QScopedPointer<MyPrivateClass> privatePtr; // QScopedPointer to forward declared class
public:
  MyClass(); // OK
  inline ~MyClass() {} // VIOLATION - Destructor must not be inline
private:
  Q_DISABLE_COPY(MyClass) // OK - copy constructor and assignment operators
                           // are now disabled, so the compiler won't implicitely generate them.
};

在Qt源码中,经常用于D指针,例如在qpainter.h中,有代码: QScopedPointer<QPainterPrivate> d_ptr,以后研究D指针时再深入探讨。

QSharedDataPointer

更像普通的指针,也是用于堆上分配的对象,但它是一个计数型只能指针,可以自由拷贝和赋值,可以共享,当此对象被一个QSharedPointer指针指向时,计数加1,少一个QSharedPointer指针指向时,计数减1,一直到计数为0时,对象才会销毁。

同QScopePointer类似,QSharedPointer也会在离开作用域后删除指针。
QSharedPointer和QWeakPointer都是线程安全的,可以原子地操作指针,不同线程可以获取这两种指针指向的对象而不必加锁。

void QSharedPointer::clear():清除所指向的对象,如果是最后一个指向,那么指针会被delete。
data函数,isNull函数同QScopePointer功能一样。

    Test* p = new Test();
    QSharedPointer<Test> p1(p);
    QSharedPointer<Test> p2(p1);
    QSharedPointer<Test> p3(p1);
    p1.clear();
    p2.clear();
    p3.clear();
    qDebug()<<"3333333";
    if(p1.isNull())
        qDebug()<<"p1 is null";
    else
        qDebug()<<"p1 is not null";
    return a.exec();

在Qt main函数中测试会遇到事件循环的问题,就是运行GUI程序的return a.exec()实际进入事件循环,没有离开作用域,这种情况下想销毁对象就需要所有指针都clear。上面的代码中,指针计数为3,所以必须三个指针都执行clear,然后才会销毁对象,即调用析构函数,然后三个指针都成为NULL。有一个指针没有clear就不会销毁对象。

假如不进事件循环,而是return 0,那么就是按作用域机制,不需要所有指针都clear也会销毁对象。

参考:
Introducing QScopedPointer

猜你喜欢

转载自blog.csdn.net/yao5hed/article/details/81092152