手动管理堆分配的对象困难且容易出错,当指针场景复杂时,手动delete难免出错,常见的结果是代码泄漏内存并且难以维护。是时候使用现代和安全的一些方法了,那就是使用智能指针,比如 QScopedPointer 或 std::unique_ptr,这些智能指针可以自动管理对象的生命周期。当智能指针的实例离开作用域时,它们会自动删除所管理的对象。
以下使用一个简单的QTime对象来演示各种内存管理方式:
1、栈中管理
这种声明方式将QTimer对象创建在栈(stack)上。栈上的对象在声明它们的函数或作用域结束时自动销毁。生命周期:由于对象是自动管理的,所以无需担心对象销毁或内存泄漏问题。当声明它的函数结束执行时,对象会自动进行清理。
void someFunction() {
QTimer timer;
connect(&timer, &QTimer::timeout, this, &MyClass::update);
timer.start(1000);
// timer在someFunction函数返回时被销毁
}
2、手动管理
通过调用 delete 来手动释放这个对象的内存。这通常在对象不再需要时进行
QTimer *timer = new QTimer();
// 使用timer进行相关操作
delete timer; // 当不再需要时,手动删除对象
3、在类中管理
如下,构函数中创建,析构函数中delete,在简单场景下并没有错;
class MyClass {
public:
MyClass() {
timer = new QTimer();
}
~MyClass() {
delete timer;
}
private:
QTimer *timer;
};
4、使用智能指针管理场景
由于使用 QScopedPointer 来管理 QTimer,这里不需要担心 QTimer 的释放,因为 QScopedPointer 会在其作用域结束时自动删除所管理的对象。因此,您可以安全地创建 QTimer 而不指定父对象,也不需要delete。
#ifndef TIMEREXAMPLE_H
#define TIMEREXAMPLE_H
#include <QObject>
#include <QTimer>
#include <QScopedPointer>
class TimerExample : public QObject {
Q_OBJECT
public:
explicit TimerExample(QObject *parent = nullptr);
~TimerExample();
private slots:
void doSomething();
private:
QScopedPointer<QTimer> timer;
};
#endif // TIMEREXAMPLE_H
#include "timerexample.h"
#include <QDebug>
TimerExample::TimerExample(QObject *parent)
: QObject(parent)
{
timer = QScopedPointer<QTimer>(new QTimer()); // 创建QTimer时不指定父对象
connect(timer.data(), &QTimer::timeout, this, &TimerExample::doSomething);
timer->start(3000);
}
TimerExample::~TimerExample() {
// 析构时QScopedPointer自动删除timer,无需手动删除
}
void TimerExample::doSomething() {
qDebug() << "Timer ticked";
}
4.1 为什么没*号,它还是指针吗?到底对象是哪个?
在使用 QScopedPointer 这样的智能指针时,声明的时候不需要在指针名前加 *,因为 QScopedPointer 本身就是一个模板类,它会自动将其模板参数类型指定为所管理的对象的指针类型。
new QTimer() 创建了一个 QTimer 对象,并将其初始化为 QScopedPointer<QTimer> 对象 timer 所管理的对象。
4.2 访问和管理成员对象时应该用箭头运算符“->”还是“*”?
timer 本身就是一个智能指针对象,它会自动管理一个 QTimer 类型的指针。这就意味着,在访问 QTimer 对象的成员函数时,需要使用 timer->,而不是 *timer.这与使用裸指针时的语法是一样的。
Qt官方示例中演示了复杂场景delete和QScopedPointer的对比,如下图:
函数有很多退出点,为了删除new出的对象,每个退出点都加入了delete,然后代码就臃肿、可读性、可维护性大大降低。用QScopedPointer改写上述代码后,简化后就是右图