Qt 之 智能指针汇总

来源
还有其他一些,做了一些汇总和测试,就不全列了。

文章目录:
一、垂悬指针的问题
二、Qt中的智能指针
       1、QPointer
       2、QSharedPointer & QWeakPointer
       3、QScopedPointer
       4、其他智能指针
三、实践记录
四、用法举例
       1、QWeakPointer举例
       2、QScopedPointer 与 std::unique_ptr 举例
       3、测试样例

代码中出现一个bug,最终发现是由于在某个特殊情况下出现了使用垂悬指针,造成了程序崩溃,进而学习了解了Qt的智能指针机制。

一、悬垂指针的问题

如图,有两个指针a和b指向同一片内存,如果删除其中一个指针a,再去使用指针b的话,程序会崩溃。因为指针b此时已经是一个垂悬指针(Dangling pointer)了,它指向的内存已经被释放不再有效。
Qt 之 智能指针汇总_第1张图片

垂悬指针使用指针b之前先判断b是否为空,这个做法在这里是不起作用的。问题的本质是通过指针a去释放内存时,指针b没有同步地置为空。
假如指针b能随内存的释放而自动置为空就好了,这正是智能指针所要解决的问题。

二、Qt中的智能指针

Qt提供了若干种智能指针:QPointer、QSharedPointer、QWeakPointer、QScopedPointer、QScopedArrayPointer、QSharedDataPointer、QExplicitlySharedDataPointer。
注:1、笔者Qt版本为4.8; 2、下述示例代码中"Plot"为"QObject"类的子类。

1、QPointer

QPointer只用于QObject的实例。如果它指向的对象被销毁,它将自动置空。
如图:
Qt 之 智能指针汇总_第2张图片

QPointer这是Qt体系下的专门用于QObject的智能指针。常见使用方法:

QPointer<Plot> a(new T());   	//构造
QPointer<Plot> a(b);		//构造
a.isNull();			//判空
a.data();			//返回裸指针

2、QSharedPointer & QWeakPointer

QSharedPointer是引用计数(强)指针,当所有强指针销毁时,实际对象才会销毁。QWeakPointer是弱指针,可以持有对QSharedPointer的弱引用。它作为一个观察者,不会引起实际对象销毁,当对象销毁时会自动置空。
这两种指针同时都有以下3个成员:强引用计数strongRef,弱引用计数weakRef和数据data。
Qt引用计数指针
Qt 之 智能指针汇总_第3张图片
Qt 之 智能指针汇总_第4张图片
Qt 之 智能指针汇总_第5张图片
QWeakPointer两种指针分别对应于C++中的std::shared_ptr和std::weak_ptr。常见使用方法://构造

QSharedPointer<Plot> a(new Plot()); 
QSharedPointer<Plot> b = a;
QWeakPointer<Plot> c = a;  //强指针构造弱指针
QWeakPointer<Plot> d(a);     

//使用

c.clear();	//清除
a.isNull();	//判空
a->func(...); 	//(按常规指针来使用 "->")
QSharedPointer<Plot> e = d.toStrongRef();   //弱指针转为强指针。注意,弱指针无法操纵数据,必须转为强指针
QWeakPointer<Plot> f = e.toWeakRef();//强指针显式转为弱指针
QSharedPointer<Plot> g = e.dynamicCast<T>();  //动态类型转换

3、QScopedPointer

QScopedPointer保证当当前范围消失时指向的对象将被删除。它拥有一个很好的名字,它向代码的阅读者传递了明确的信息:这个智能指针只能在本作用域里使用,不希望被转让,因为它的拷贝构造和赋值操作都是私有的。相当于C++中的std::unique_ptr,实例代码:

func(){
    Plot* plot = new Plot();
    //QScopedPointer出作用域自动销毁内存
    QScopedPointer<Plot>qsp(plot);
    //plot没有内存泄漏
}

4、其他智能指针

QScopedArrayPointer:一个QcopedPointer,默认删除它指向Delete []运算符的对象。为方便起见,还提供了操作符[]。QSharedDataPointer/QExplicitySharedDataPointer搭配QSharedData类一起使用,以实现自定义隐式共享或显式共享类。

三、实践记录

1、通常,要使用弱指针,必须将其转换为强指针,因为这样的操作确保了只要您使用它就会生存。这相当于“锁定”访问的对象,并且是使用弱指针指向的对象的唯一正确方法。并且转换后使用前需要判空。

QSharedPointer<Plot> qsp = qwp.toStrongRef();     //qwp是QWeakPointer
if(!qsp.isNull()){ 
qDebug() << qsp->getName(...);  //使用指向的对象
        //...
}

2、最好在new的时候就用QSharedPointer封装,并管理起来。

QSharedPointer<Plot> qsp = QSharedPointer(new Plot());

3、使用智能指针包装后,不要直接去删除指针对象。

Plot* plot = new Plot();
QSharedPointer<Plot> qsp1(plot);
delete plot;  //运行时会提示:"shared QObject was deleted directly. The program is malformed and may crash."

4、不要多次使用同一裸指针构造QSharedPointer。Plot *plot = new Plot();

QSharedPointer<Plot> qsp(plot);
QSharedPointer<Plot> qsp_ok = qsp;
QSharedPointer<Plot> qsp_error(plot);   //崩溃,输出: “pointer 0x1f0a8f0 already has reference counting”

5、不要使用new直接构造QWeakPointer对象,它只能通过QSharedPointer的赋值来创建。QWeakPointer a(new Plot()); //error: 引用计数:强-1 弱2
6、由于智能指针对象是值语义,参数传递时尽可能用const引用兼顾效率
7、关于动态转换。使用QSharedPointer::dynamicCast()方法。
8、关于智能指针与QVariant转换,并关联到qobject的userdata。//注册到元对象

Q_DECLARE_METATYPE(QWeakPointer<Plot>) 				
//设置数据
item->setData(QVariant::fromValue(plot.toWeakRef()), Qt::UserRole);	
//取数据
QWeakPointer<Plot> plot = treeModel->data(index, Qt::UserRole).value<QWeakPointer<BasePlot> >();

9、关于Qt元对象系统自动析构和Qt智能指针自动析构相冲突的问题,经初步实验Qt4.8中应该已经解决了?不过实际中,可以让数据用智能指针管理,不用父子层级;窗体控件用父子层级,不用智能指针。

四、 用法举例

1、QWeakPointer举例

QWeakPointer不能用于直接取消引用指针,但它可用于验证指针是否已在另一个上下文中被删除。并且QWeakPointer对象只能通过QSharedPointer的赋值来创建。

需要注意的是,QWeakPointer不提供自动转换操作符来防止错误发生。即使QWeakPointer跟踪指针,也不应将其视为指针本身,因为它不能保证指向的对象保持有效。

说了那么多,QWeakPointer到底有什么用呢?
答案就是:解除循环引用。

在概述中我们说到,QWeakPointer 是为配合 QSharedPointer 而引入的一种智能指针。而什么叫循环引用,就是说:两个对象互相使用一个 QSharedPointer成员变量指向对方(你中有我,我中有你)。由于QSharedPointer是一个强引用的计数型指针,只有当引用数为0时,就会自动删除指针释放内存,但是如果循环引用,就会导致QSharedPointer指针的引用永远都不能为0,这时候就会导致内存无法释放。
所以QWeakPointer诞生了,它就是为了打破这种循环的。并且,在需要的时候变成QSharedPointer,在其他时候不干扰QSharedPointer的引用计数。它没有重载 * 和 -> 运算符,因此不可以直接通过 QWeakPointer 访问对象,典型的用法是通过 lock() 成员函数来获得 QSharedPointer,进而使用对象。

示例
首先,我们来看一个QSharedPointer循环使用的示例:

#include 
#include 
#include 
#include 

class Children;
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;

这时候再次运行,就会看到输出:

~Children
~Parent

这是因为在test()退出之前引用计数是1,函数退出之后就自动析构,这就解除了上面的循环引用。

2、QScopedPointer 与 std::unique_ptr

它们概念上应该是是一样的。下面不再区分:

这是一个很类似auto_ptr的智能指针,它包装了new操作符在堆上分配的动态对象,能够保证动态创建的对象在任何时候都可以被正确地删除。但它的所有权更加严格,不能转让,一旦获取了对象的管理权,你就无法再从它那里取回来。

无论是QScopedPointer 还是 std::unique_ptr 都拥有一个很好的名字,它向代码的阅读者传递了明确的信息:这个智能指针只能在本作用域里使用,不希望被转让。因为它的拷贝构造和赋值操作都是私有的,这点我们可以对比QObject及其派生类的对象哈。

用法 (来自Qt的manual):

考虑没有智能指针的情况,

 void myFunction(bool useSubClass)
 {
     MyClass *p = useSubClass ? new MyClass() : new MySubClass;
     QIODevice *device = handsOverOwnership();

     if (m_value > 3) {
         delete p;
         delete device;
         return;
     }

     try {
         process(device);
     }
     catch (...) {
         delete p;
         delete device;
         throw;
     }

     delete p;
     delete device;
 }

我们在异常处理语句中多次书写delete语句,稍有不慎就会导致资源泄露。采用智能指针后,我们就可以将这些异常处理语句简化了:

 void myFunction(bool useSubClass)
 {
     QScopedPointer<MyClass> p(useSubClass ? new MyClass() : new MySubClass);
     QScopedPointer<QIODevice> device(handsOverOwnership());

     if (m_value > 3)
         return;

     process(device);
 }

另,我们一开始的例子,也是使用这两个指针的最佳场合了(出main函数作用域就将其指向的对象销毁)。

注意:因为拷贝构造和赋值操作私有的,它也具有auto_ptr同样的“缺陷”——不能用作容器的元素。

3、测试样例

#include 
#include 
#include 
#include 
#include 
#include 
int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);
    //raw pointer
    //QString *p = new QString("hello");
    QSharedPointer<QString> a(new QString("hello"));
    //QSharedPointer b = a;
    QWeakPointer<QString> c = a;  //强指针构造弱指针
    QWeakPointer<QString> d(a);

    //使用
    a.clear();
    //c.clear();	//清除
    if(!c.toStrongRef().isNull())
    {
        qDebug() << c.isNull() << c.toStrongRef()->length();
    }
    else {
        qDebug() << "is null";
    }
    qDebug() << a.isNull();	//判空
    qDebug() << a->length()<<*(a.data()); 	//(按常规指针来使用 "->")
    QSharedPointer<QString> e = d.toStrongRef();   //弱指针转为强指针。注意,弱指针无法操纵数据,必须转为强指针
    QWeakPointer<QString> f = e.toWeakRef();//强指针显式转为弱指针
    QSharedPointer<QString> g = e.dynamicCast<QString>();  //动态类型转换
//    QScopedPointer t2(p);
//    QScopedPointer t3(p);

//    t3.reset();
//    qDebug() << t3.data();
//    qDebug() << *(t1.data());
//    qDebug() << *(t2.take()->data());
    //Implements non-reference-counted strong pointer
//    QScopedPointer pScopedPointer(new QString("Scoped"));
//    // Build error, can NOT be shared and reference-counted
//    //QScopedPointer pScopedPointerpScopedPointer2 = pScopedPointer;
//    //Implements reference-counted strong sharing of pointers
//    QSharedPointer pSmart(new QString("Smart"));
//    QSharedPointer pSmart2;
//    pSmart2 = QSharedPointer(new QString("smart 2"));
//    QSharedPointer pSharedPoninter;
//    // can be shared safely and reference-counted
//    pSharedPoninter = pSmart;
//    qDebug() << *(pSmart.data());
//    qDebug() << *(pSmart2.data());
//    qDebug() << *(pSharedPoninter.data());
//    qDebug() << pSharedPoninter.stro
//    QTimer *t = new QTimer;
//    QSharedPointer timer(t);
//    QWeakPointer pWeakPointer = timer;
//    pWeakPointer.data()->start(2000);
//    //Weak pointer's resources can be deleted from outside world
//    delete t;
//    qDebug() << timer.data();
//    if (pWeakPointer.isNull())
//    {
//        qDebug() << "contained QObject has been deleted";
//    }
}

你可能感兴趣的:(《Qt,项目实战经历全记录》,qt,智能指针)