文章目录
- 1. 概述
- 2. Qt 中有几种智能指针?
- 2.1 QSharedPointer 实例
- 2.2 QSharedPointer 与 QWeakPointer 实例
- 2.3 QScopedPointer 实例
- 2.4 QPointer 实例
1. 概述
在使用动态内存分配的情况下,需要确保对象的所有权正确地被管理和转移。使用智能指针可以帮助我们自动管理对象的生命周期和所有权,避免内存泄漏和悬挂指针的问题。
♦ 什么时候需要用到智能指针?
-
在使用QObject对象的情况下,需要确保对象的生命周期和父子关系正确地被管理。QObject对象的生命周期受到父子关系的影响,因此需要使用QPointer等智能指针来管理QObject对象的指针。
-
多线程编程中,需要确保多个线程访问共享对象时不会发生竞态条件。使用智能指针可以避免竞态条件的发生,因为它会自动对对象进行引用计数,确保在所有引用都被释放后才会删除对象
-
在使用异常处理时,需要确保在函数返回时所有动态分配的对象都被正确地删除。使用智能指针可以确保在函数返回时所有动态分配的对象都被正确地删除,以避免内存泄漏。
-
防止忘记调用 delete
总之,当我们需要动态分配内存并且需要确保对象的生命周期、所有权、线程安全性等方面的正确管理时,就可以考虑使用智能指针。
2. Qt 中有几种智能指针?
常用的包括 QSharedPointer、QWeakPointer、QScopedPointer和QPointer;
- QSharedPointer 适用于共享所有权的情况
- QWeakPointer 适用于不共享所有权但需要访问QSharedPointer所管理的对象的情况
- QScopedPointer 适用于独占所有权的情况。
- QPointer 主要用于解决指针的空悬问题,适用于Qt对象之间的引用
具体介绍如下:
2.1 QSharedPointer 实例
QSharedPointer:强引用,大体相当于C++11 标准中的 shared_ptr, 用于管理动态分配的对象的共享所有权,即多个QSharedPointer对象可以指向同一个对象,并共享该对象的内存管理。
它使用引用计数来追踪对象的使用情况,当最后一个QSharedPointer对象被销毁时,它将自动释放对象的内存。由于使用了引用计数,QSharedPointer能够自动处理指针的生命周期,避免内存泄漏和空悬指针等问题,因此是Qt中最常用的智能指针。
#include <QCoreApplication>
#include <QSharedPointer>
#include <QDebug>
class MyClass
{
public:
MyClass(int value) : m_value(value) {}
void setValue(int value) { m_value = value; }
int getValue() const { return m_value; }
private:
int m_value;
};
int main()
{
QSharedPointer<MyClass> pointer1(new MyClass(10)); // 引用计数为1
{
QSharedPointer<MyClass> pointer2 = pointer1; // 引用计数为2
} // pointer2销毁,引用计数为1
qDebug() << "Value:" << pointer1->getValue(); // 输出10
{
QSharedPointer<MyClass> pointer3 = pointer1; // 引用计数为2
pointer1.clear(); // 引用计数为1
qDebug() << "Value:" << pointer3->getValue(); // 输出10
QWeakPointer<MyClass> weak(pointer3);
} // pointer3销毁,引用计数为0,MyClass对象被自动释放
return 0;
}
输出结果:
需要注意的是,QSharedPointer只能管理动态分配的对象的内存。如果我们将其用于指向栈对象或全局对象,那么它就不会自动释放对象的内存,这可能会导致程序崩溃或内存泄漏。
深入了解:C++ 智能指针(shared_ptr/weak_ptr)源码分析
2.2 QSharedPointer 与 QWeakPointer 实例
QWeakPointer:弱引用,大体相当于C++11 标准中的 weak_ptr ,用于在不共享所有权的情况下访问QSharedPointer指向的对象。QWeakPointer指向QSharedPointer所管理的对象,但不会增加对象的引用计数,也不会影响对象的生命周期。当对象被释放时,QWeakPointer会自动被置为空指针,避免了空悬指针的问题。
QWeakPointer 是为配合 QSharedPointer 而引入的一种智能指针,它更像是 QSharedPointer 的一个助手,像一个旁观者一样来观测资源的使用情况。
#include <QSharedPointer>
#include <QWeakPointer>
#include <QDebug>
class MyClass
{
public:
MyClass(int value) : m_value(value) {
qDebug() << "MyClass constructor called with value" << m_value;
}
~MyClass() {
qDebug() << "MyClass destructor called with value" << m_value;
}
int getValue() const {
return m_value;
}
private:
int m_value;
};
int main()
{
QSharedPointer<MyClass> shared(new MyClass(20));
QWeakPointer<MyClass> weak(shared);
qDebug() << "Shared pointer value:" << shared->getValue();
qDebug() << "Weak pointer value:" << weak.data()->getValue();
shared.clear();
if (weak.isNull()) {
qDebug() << "Weak pointer is null - object has been deleted"; //执行
} else {
qDebug() << "Weak pointer is not null - object still exists";
}
return 0;
}
打印结果:
例中 创建了一个QSharedPointer对象和一个QWeakPointer对象,它们都指向一个MyClass对象。在main函数的结尾处,使用 shared.clear() 来释放 MyClass 对象的所有权。此时,MyClass对象的引用计数为0,将被自动删除,而此时 QWeakPointer 对象 weak 也为null。
2.3 QScopedPointer 实例
QScopedPointer 类似于 C++ 11 中的 unique_ptr 用于管理动态分配的对象的独占所有权,即同一时间只能有一个QScopedPointer指向该对象。
QScopedPointer使用了RAII(资源获取即初始化)技术,当QScopedPointer被销毁时,它将自动释放所管理的对象的内存。QScopedPointer不支持拷贝和赋值操作,这是为了避免在多个指针之间共享所有权的问题。如果需要在多个指针之间转移所有权,应该使用QSharedPointer或QWeakPointer。
如下简单示例:
#include <QScopedPointer>
#include <QDebug>
class MyClass
{
public:
MyClass(int value) : m_value(value) {
qDebug() << "MyClass constructor called with value" << m_value;
}
~MyClass() {
qDebug() << "MyClass destructor called with value" << m_value;
}
int getValue() const {
return m_value;
}
private:
int m_value;
};
int main()
{
QScopedPointer<MyClass> myObject(new MyClass(23));
qDebug() << "My object value:" << myObject->getValue();
return 0;
}
结果如下:
2.4 QPointer 实例
由于QObject对象的生命周期受到父子关系的影响,当父对象被删除时,所有子对象也会被删除。使用 QPointer 可以避免在父对象被删除时,子对象的指针指向已经被删除的对象的问题。
通常情况下,我们在手动delete一个指针的时候,需要再将其置空,要不然会变成一个悬挂的野指针,那么QPointer就是帮忙干这事的,会在对象被销毁时,自动设置为NULL
♦ 场景一:
//
// myqpointer.cpp
//
#include "myqpointer.h"
MyQPointer::MyQPointer(QObject *parent) : QObject(parent)
{
qDebug() << "MyObject constructor called";
}
MyQPointer::~MyQPointer()
{
qDebug() << "MyObject destructor called";
}
void MyQPointer::doSomething()
{
qDebug() << "MyObject is doing something";
}
//
// myqpointer.h
//
#ifndef MYQPOINTER_H
#define MYQPOINTER_H
#include <QObject>
#include <QPointer>
#include <QDebug>
class MyQPointer : public QObject
{
Q_OBJECT
public:
explicit MyQPointer(QObject *parent = nullptr);
~MyQPointer();
void doSomething();
};
#endif // MYQPOINTER_H
//
// main.cpp
//
#include <QCoreApplication>
#include "myqpointer.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
MyQPointer* myQPointer = new MyQPointer();
delete myQPointer;
// myQPointer = nullptr;
if (myQPointer) {
qDebug() << "\nMyObject is valid";
myQPointer->doSomething();
} else {
qDebug() << "\nMyObject is null";
}
return a.exec();
}
运行结果:
delete pLabel; 过后如果不手动置空,这里输出将是 “MyObject is valid”。
使用智能指针 QPointer,更改下mian.cc:
//
// main.cpp
//
#include <QCoreApplication>
#include "myqpointer.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QPointer<MyQPointer> myQPointer(new MyQPointer);
delete myQPointer;
// myQPointer = nullptr;
if (myQPointer) {
qDebug() << "\nMyObject is valid";
myQPointer->doSomething();
} else {
qDebug() << "\nMyObject is null";
}
return a.exec();
}
运行结果:
写法很简单,就是直接将
MyQPointer* myQPointer = new MyQPointer();
替换成
QPointer<MyQPointer> myQPointer(new MyQPointer);
在main函数的结尾处,我们调用了MyObject对象的deleteLater()函数,将其标记为待删除状态。然后我们再次检查QPointer对象是否为空,此时它应该返回一个空指针
♦ 场景二:
将上述 main.cc 中修改如下:
#include <QCoreApplication>
#include "myqpointer.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
MyQPointer* myQPointer1 = new MyQPointer();
MyQPointer* myQPointer2 = myQPointer1;
delete myQPointer1;
myQPointer1 = nullptr;
if (myQPointer1) {
qDebug() << "myQPointer1 is valid";
} else {
qDebug() << "myQPointer1 is null";
}
if (myQPointer2) {
qDebug() << "myQPointer2 is valid";
} else {
qDebug() << "myQPointer2 is null";
}
return a.exec();
}
运行结果:
上面这种情况,myQPointer2 是直接复制myQPointer1,指向同一个地址,这时候delete myQPointer1过后,就不需要再delete myQPointer2 ,否则将会报错,但是 myQPointer2 也需要手动置空,否则变成悬挂的野指针,实际情况中可能经常会忘记置空,甚至将指针delete两次,对于新手来说,这种错误是常犯的。
将上述:
MyQPointer* myQPointer2 = myQPointer1;
修改为:
QPointer<MyQPointer> myQPointer2(myQPointer1);
//QPointer<MyQPointer> myQPointer2 = myQPointer1 ; //两者都行
运行结果:
当然最好的方式是,myQPointer1 和 myQPointer2都用智能指针的方式,这样就不用手动置空了。这是 QPointer 最常见的一种使用场景。
♦ 参考:
Qt 中的智能指针
Qt智能指针–QPointer