QT 学习:QT中的3种指针介绍与使用

发布时间:2024-02-11 19:30

QPointer

QPointer是Qt提供的一个比较特别的智能指针,和其它智能指针有很大的不同,该智能指针专门为自动释放内存资源而设计的。

正文

QPointer本质是一个模板类,属于Qt对象模型的特性,它为QObject提供了guarded pointer,当其指向的对象被销毁时,它会被自动置NULL。

需要注意的是:QPointer所指向的对象必须是QObject或其派生类对象。 因为其对象析构时会执行QObject的析构函数,进而执行QObjectPrivate::clearGuards(this);

通常情况下,我们在手动delete一个指针的时候,需要再将其置空,要不然会变成一个悬挂的野指针,那么QPointer就是帮忙干这事的,会在对象被销毁时,自动设置为NULL。

QPointer 属于Qt Object模型的核心机制之一,请注意和其他智能指针的区别。

来看一个示例对比:
不用智能指针:

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QLabel * pLabel = new QLabel();
    delete pLabel;
    if(pLabel){
        qDebug()<< "pLabel is not null";
    }
    else{
        qDebug()<< "pLabel is null";
    }
    return a.exec();
}

delete pLabel;过后如果不手动置空,这里输出将是"pLabel is not null"。

使用智能指针QPointer:

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QPointer pLabel = new QLabel() ;
    delete pLabel;
    if(pLabel){
        qDebug()<< "pLabel is not null";
    }
    else{
        qDebug()<< "pLabel is null";
    }
    return a.exec();
}

再次运行输出"pLabel is null"

写法很简单,就是直接将
QLabel * pLabel = new QLabel();
替换成
QPointer pLabel = new QLabel() ;
即可。

使用场景

除了上面我们看到的常规用法之外,还有别的更常用的场景吗?当然有。

当你需要保存其他人所拥有的QObject对象的指针时,这点非常有用。

怎么理解这句话呢?
来看一个示例:

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QLabel * pLabel = new QLabel();
    pLabel->setText("aa");

    QLabel * pLabel2 = pLabel;

    delete pLabel;
    pLabel = nullptr;

    if(pLabel){
        qDebug()<< "pLabel is not null";
    }
    else{
        qDebug()<< "pLabel is null";
    }
    if(pLabel2){
        qDebug()<< "pLabel2 is not null";
    }
    else{
        qDebug()<< "pLabel2 is null";
    }
    return a.exec();
}

输出:

pLabel is null
pLabel2 is not null

上面这种情况,pLabel2是直接复制pLabel,指向同一个地址,这时候delete pLabel过后,就不需要再delete pLabel2,否则将会报错,但是pLabel2也需要手动置空,否则变成悬挂的野指针,实际情况中可能经常会忘记置空,甚至将指针delete两次,对于新手来说,这种错误是常犯的。

那么,如果将pLabel2改成QPointer智能指针就可以有效避免这种情况:

直接将上述程序中:

QLabel * pLabel2 = pLabel;

改成

QPointer pLabel2 = pLabel ;

再次运行输出:

pLabel is null
pLabel2 is null

当然最好的方式是,pLabel和pLabel2都用智能指针的方式,这样就不用手动置空了。这是QPointer最常见的一种使用场景。

一个细节

通常我们的一个指针对象想要调用其成员函数时,通常的写法是:变量+“.”,然后“.”会自动变成 “->”,但是到了QPointer指针时却不是这样,QPointer本身还提供了几个成员函数,还是用上面的label举例:

QPointer pLabel = new QLabel() ;

pLabel是我们申请出来的QLabel对象,如果想要使用QLabel的成员函数怎么调用呢?
两种方法:

  • 通过data()调用。data()是QPointer的成员变量,它返回指向的指针对象,通过指针对象对其成员变量引用,比如:pLabel.data()->setText(“aa”);

  • 直接引用。上面说到,我们通常要调用指针对象的成员变量或函数时,通常的写法是:变量+“.”,然后“.”会自动变成 “->”,但是QPointer却不是,这里我们写“pLabel.” 自动联想出来的是QPointer本身的成员函数,而非QLabel的成员函数,并且“.”也不会变成"->",那么如果我们要调用QLabel的成员函数,就得手动写"->",比如:pLabel->setText(“aa”);

再详细点描述一下这种使用方式,通过两个对比就很明白了。
QT 学习:QT中的3种指针介绍与使用_第1张图片 QT 学习:QT中的3种指针介绍与使用_第2张图片

我们看到这两个的对比,对于QPointer的对象,写“.”和“->”所调用的成员变量是不同的,一个是QPointer对象本身,一个是QPointer指向的指针对象。特意把这个细节提出来,就是为了提醒一下,可能平时写代码习惯了,指针引用“.”,反正编辑器会自动变成,但是到了QPointer会有点区别。

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

QScopedPointer

概述

前一篇文章我们详细的介绍了QPointer的用法,那么,这里继续总结Qt的另一个智能指针QScopedPointer的用法。

QScopedPointer和C++中的智能指针std::unique_ptr其概念是一样的,它包装了new操作符在堆上分配的动态对象,能够保证动态创建的对象在任何时候都可以被正确地删除。但它有更严格的所有权,并且不能转让,一旦获取了对象的管理权,你就无法再从它那里取回来。也就是说,只要出了作用域,指针就会被自动删除,因为它的拷贝构造和赋值操作都是私有的,与QObject及其派生类风格相同。

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

上面的写法,稍有不慎就会导致内存泄露,但是如果使用智能指针,就会变得很简单了:

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

     if (m_value > 3)
         return;

     process(device);
 }

注意:因为拷贝构造和赋值操作私有的,所以不能用作容器的元素。

const 限制

C ++指针的const限定也可以用QScopedPointer表示:

    const QWidget *const p = new QWidget();
    // 等同于:
    const QScopedPointer p(new QWidget());

    QWidget *const p = new QWidget();
    // 等同于:
    const QScopedPointer p(new QWidget());

    const QWidget *p = new QWidget();
    // 等同于:
    QScopedPointer p(new QWidget());

考虑一种情况

上面说到,使用QScopedPointer智能指针动态创建的对象,一旦出了作用域就会 被自动释放并置空,那么如果需要函数返回值怎么办呢?

比如下面这种情况:

QLabel * createLabel()
{
    QScopedPointer pLabel(new QLabel());
//    return pLabel.data();  //invalid
    return  pLabel.take(); //valid
}

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QScopedPointer p1(createLabel());
    p1->setText("hello");
    p1->show();

    return a.exec();
}

注意,我们在createLabel()函数中创建label对象并返回时,不能使用data(),而要使用take();
因为 T *QScopedPointer::data() const返回指向对象的常量指针,QScopedPointer仍拥有对象所有权。 所以通过data()返回过后就被自动删除了,从而导致mian函数中的p1变成了野指针,程序崩溃。
而使用T *QScopedPointer::take()也是返回对象指针,但QScopedPointer不再拥有对象所有权,而是转移到调用这个函数的caller,同时QScopePointer对象指针置为NULL。

另外还有一个函数要注意。
void QScopedPointer::reset(T *other = Q_NULLPTR):delete目前指向的对象,调用其析构函数,将指针指向另一个对象other,所有权转移到other。

QScopedArrayPointer

对应的还有一个指针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其作用是一样的,其应用范围比我们前面说到的QPointer和QScopedPointer更广;

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 obj =
            QSharedPointer(new MyObject, doDeleteLater);

        // continue using obj
        obj.clear();    // calls obj->deleteLater();
    }

这个示例中,传入了一个函数,用于自定义删除。

也可以直接使用成员函数:

QSharedPointer obj =
        QSharedPointer(new MyObject, &QObject::deleteLater);

QSharedPointer使用非常方便,直接和普通指针用法一样,创建后直接用,后面就不用管了。

再来看一个自己写的简单示例:

class Student : public QObject
{
    Q_OBJECT
public:
    Student(QObject * parent = nullptr);
    ~Student();
};

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = 0);
    ~Widget();
private:
    QSharedPointer m_pStudent;

};

#include "widget.h"
#include 

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    qDebug() << __FUNCTION__;
    m_pStudent = QSharedPointer(new Student());
}

Widget::~Widget()
{
    qDebug() << __FUNCTION__;
}

Student::Student(QObject *parent):
    QObject (parent)
{
    qDebug() << __FUNCTION__;
}

Student::~Student()
{
    qDebug() << __FUNCTION__;
}

运行后关闭窗口,输出:

Widget
Student
~Widget
~Student

可以看到,student对象被自动释放了。

ItVuer - 免责声明 - 关于我们 - 联系我们

本网站信息来源于互联网,如有侵权请联系:561261067@qq.com

桂ICP备16001015号