• Qt各种指针的使用总结


    1、前言

    C++编程难点之一就是内存管理,尤其是对于指针的使用,管理不好很容易出现内存泄露。我们使用Qt框架开发软件时,可以用Qt封装的几种智能指针,这些指针将C++指针封装到一个对象里,使用方式与普通指针一样。这种将指针封装成对象的方式,避免了直接使用指针可能导致的内存泄漏。本文总结了QPointer、QSharedPointer、QWeakPointer、QSharedDataPointer、QScopedPointer的使用场景及示例,理解并熟练使用这些指针,能够帮助我们写出更健壮的程序。

    2、QPointer

    QPointer 是一个模版类,主要用来管理QObject子类的指针,自定义类如果不是QObject的子类,则不能使用QPointer管理。当QPointer管理的指针被销毁时,它会自动设置为0。使用示例

          QPointer<QLabel> label = new QLabel;
          label->setText("&Status:");
          ...
          if (label)
              label->show();
    
    • 1
    • 2
    • 3
    • 4
    • 5

    编程时如果一个类A持有另一个类B的指针,而不知道B指针何时会销毁,此时可以用QPointer来保存这个指针,使用的时候用if语句判断一下,如果指针有效再执行下面的操作。

    注意:QPointer管理的必须是QObject子类的指针

    3、QSharedPointer

    QSharedPointer 共享指针的强引用,通过引用计数自动地管理共享指针,它的行为与正常指针一样,即使用const修饰的QSharedPointer与普通指针行为也一样。当一个QSharedPointer对象超出作用域时,若没有其他的QSharedPointer对象引用它管理的指针,即引用计数为0时,它将自动删除持有的指针。下面举例说明如何使用:

     {
            QSharedPointer<QLabel> pLabel(new QLabel);
            pLabel->setText(QString("test"));
            //ref:1
    
            QSharedPointer<QLabel> pLabel2 = pLabel;
            //ref:2
    
            QWeakPointer<QLabel> weakLabel = pLabel2;
             //ref:2
        }
        //ref:0 超出作用于自动释放pLabel管理的内存
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    下面是错误的用法,将同一个指针给多个QSharedPointer,会导致程序崩溃。

    	{
             QSharedPointer<QLabel> pLabel(new QLabel);
             
             //注意不能将同一个指针给两个QSharedPointer,可以编译通过,但是会导致程序崩溃
             QSharedPointer<QLabel> pLabel2(pLabel.data());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    4、QWeakPointer

    QWeakPointer 共享指针的弱引用,不能直接用于访问共享指针,但是可以用来验证指针是否在其他的上下文环境中被删除。当多个两个或多个类中有各自指针的QSharedPointer引用时,会形成循环引用,导致两个或多个类对象都无法释放,这种情况可以引入QWeakPointer来解除循环引用。使用示例:

        QWeakPointer<QLabel> pLabel;
        {
            QSharedPointer<QLabel> strongLabel(new QLabel);
    
            pLabel = strongLabel;
            if(pLabel.toStrongRef())
            {
                pLabel.data()->setText("Hello");
                qDebug() << "pWeakLabel is valid"; //打印这行
            }
        }
        //pLabel 指向的strongLabel超过作用域已经释放了。
        {
            QWeakPointer<QLabel> pWeakLabel(pLabel);
            
            if(pWeakLabel.toStrongRef()) 
            {
    	         pWeakLabel.data()->setText("Hello");
    	         qDebug() << "pWeakLabel is valid";
            }
            else
            {
             	qDebug() << "pWeakLabel is unvalid"; //打印这行
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    下面是一个循环引用的示例:

    class B;
    class A
    {
    public:
        void setB(const QSharedPointer<B> &B);
    private:
        QSharedPointer<B> m_b;   //强引用
    };
    
    class B
    {
    public:
        void setA(const QSharedPointer<A> &A);
    private:
        QSharedPointer<A> m_a; //强引用
    };
    
    {
    	//这段代码产生了循环引用,导致a,b超过了作用域后内存不能被释放
      	QSharedPointer<A> a(new A);
        QSharedPointer<B> b(new B);
        a->setB(b);
        b->setA(a);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    上面举的例子很简单,比较容易发现问题,实际开发中可能是3个以上的类相互引用而产生循环引用,这时候问题就藏的比较深了,需要认真分析找出循环引用的环,然后用QWeakPointer打破这个环。解除循环引用示例:

    class B;
    class A
    {
    public:
        void setB(const QSharedPointer<B> &B);
    private:
        QSharedPointer<B> m_b;   //强引用
    };
    
    class B
    {
    public:
        void setA(const QSharedPointer<A> &A);
    private:
        QWeakPointer<A> m_a; //弱引用
    };
    
    {
    	//由于一方使用了QWeakPointer,不会产生循环引用,a,b超过了作用域后内存能被释放
      	QSharedPointer<A> a(new A);
        QSharedPointer<B> b(new B);
        a->setB(b);
        b->setA(a);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    5、QSharedDataPointer

    QSharedDataPointer 主要用来管理隐式共享对象,隐式共享类是指一个类里有一个QSharedDataPointer管理的对象,这个对象继承自QSharedData。举例说明

    class EmployeeData : public QSharedData
      {
        public:
          EmployeeData() : id(-1) { }
          EmployeeData(const EmployeeData &other)
              : QSharedData(other), id(other.id), name(other.name) { }
          ~EmployeeData() { }
    
          int id;
          QString name;
      };
    
      class Employee
      {
        public:
          Employee() { d = new EmployeeData; }
          Employee(int id, const QString &name) {
              d = new EmployeeData;
              setId(id);
              setName(name);
          }
          Employee(const Employee &other)
                : d (other.d)
          {
          }
          void setId(int id) { d->id = id; }
          void setName(const QString &name) { d->name = name; }
    
          int id() const { return d->id; }
          QString name() const { return d->name; }
    
        private:
          QSharedDataPointer<EmployeeData> d;
      };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    上例中Employee 即是隐式共享类。QSharedDataPointer 实现了引用计数来管理对象,Qt中有很多类具备隐式共享的性质,隐式共享可以提高内存使用效率。默认情况下一个隐式共享类的对象在程序中传递时,在没有修改数据的情况下,内存中只有一份数据拷贝。隐式共享类还有个特性是写时复制,例如

    Employee a(1001, "张三");
    Employee b = a;
    //此时内存中只有一个 EmployeeData 对象。
    
    b.setName("李四");
    //此时a、b的数据是完全独立的两个对象了,此时内存中有量个 EmployeeData 对象
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    上例的写时复制在逻辑上可能是有问题的,当调用了b.setName()之后,会产生两个id为1001,但名字不同的对象出来。如果你只是想在任何地方都能修改id为1001的雇员的名字,而不是生成两个对象,那就不能用隐式共享了,需要用显示共享。显示共享就是把

    QSharedDataPointer<EmployeeData> d; 
    //改为
    QExplicitlySharedDataPointer<EmployeeData> d;
    
    • 1
    • 2
    • 3

    用了显示共享后下面的代码就会有新的含义了

    Employee a(1001, "张三");
    Employee b = a;
    //此时内存中只有一个 EmployeeData 对象。
    
    b.setName("李四");
    //此时内存中仍然只有一个 EmployeeData 对象
    //保证了id为1001的只有一个数据实例。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    6、QScopedPointer

    QScopedPointer 保存一个动态分配内存的对象指针,当QScopedPointer作用域失效时,自动通过析构函数来删除其管理的指针。
    不用智能指针编程:

    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;
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    使用QScopedPointer 管理指针

    
      void myFunction(bool useSubClass)
      {
          QScopedPointer<MyClass> p(useSubClass ? new MyClass() : new MySubClass);
          QScopedPointer<QIODevice> device(handsOverOwnership());
    
          if (m_value > 3)
              return;
          process(device);
      }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    从上面的示例可以看出,QScopedPointer作为一个小工具类,能极大的简化编码。这里把堆内存的分配赋予了栈内存的所有权,即资源请求即初始化(RAII),超过栈的作用域就清理指针对应的内存。 QScopedPointer 没有拷贝构造函数和赋值操作符,因此指针的所有权和生命周期能清晰的表示出来,即QScopedPointer对象的生命周期。

    以上就是本篇的所有内容了,有问题的朋友欢迎留言讨论!!!

  • 相关阅读:
    2022年09月 C/C++(七级)真题解析#中国电子学会#全国青少年软件编程等级考试
    全网最全谷粒商城记录_06、环境-使用vagrant快速创建linux虚拟机——2、vagrant镜像仓库、下载、安装、验证
    医疗健康产品展:联影医疗
    Java基础(四)
    云架构的一些核心概念
    AXI协议详解(7)-响应信号
    Linux常用快捷键
    elementUI文档的小细节怎么实现?
    mapbox支持的坐标系
    飞桨产业级开源模型库:加速企业AI任务开发与应用
  • 原文地址:https://blog.csdn.net/zheng19880607/article/details/127721898