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();

编程时如果一个类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管理的内存

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

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

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"; //打印这行
        }
    }

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

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);
}

上面举的例子很简单,比较容易发现问题,实际开发中可能是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);
}

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;
  };

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

Employee a(1001, "张三");
Employee b = a;
//此时内存中只有一个 EmployeeData 对象。

b.setName("李四");
//此时a、b的数据是完全独立的两个对象了,此时内存中有量个 EmployeeData 对象

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

QSharedDataPointer<EmployeeData> d; 
//改为
QExplicitlySharedDataPointer<EmployeeData> d;

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

Employee a(1001, "张三");
Employee b = a;
//此时内存中只有一个 EmployeeData 对象。

b.setName("李四");
//此时内存中仍然只有一个 EmployeeData 对象
//保证了id为1001的只有一个数据实例。

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;
  }

使用QScopedPointer 管理指针


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

      if (m_value > 3)
          return;
      process(device);
  }

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

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

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凝望星辰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值