一、前言
Qt及QtWidget框架采用c++作为开发语言,它的内存管理遵循c++的内存管理原则,同时Qt也推出了一套自己的内存管理机制用以提高内存管理的效率
二、手动内存管理
1、栈内存和堆内存
栈内存的管理是由操作系统自行完成的,生存期由所在作用域决定,超出作用域的栈内存变量会被自动释放。堆内存是通过new或者maloc()在堆区分配的内存,需要手动释放
class MyPixmap:public QPixmap{
public:
MyPixmap(const QString& fileName):QPixmap(fileName) {
}
~MyPixmap(){
qDebug() << "释放了pixmap";
}
};
class MyQPushButton:public QPushButton{
public:
explicit MyQPushButton(QWidget *parent = nullptr):QPushButton(parent) {
}
~MyQPushButton(){
qDebug() << "释放了pushbutton";
}
};
void addButton(){
auto *backButton = new QPushButton;
MyPixmap pMap(":/navbar.home.svg");
backButton->setIcon(pMap);
}
pMap对象离开函数作用域就会被自动释放。backButton则由于没有调用delete造成了内存泄漏
三、基于QObject的子对象列表内存管理
QObject类在其内部维护了一个子对象列表,在其析构函数中调用delete对所有的子对象进行释放。
一个QObject对象,在初始化时指定了parent,那么当parent被释放时,该对象也会随着parent的释放被释放。这个机制对于不需要声明为类成员变量但是又希望它随着类的释放一起释放还是挺方便的。
实际上QWidget框架的很多布局相关接口都会将指针添加到子对象列表中,如下代码:
class EditorToolBarView : public QWidget{
public:
EditorToolBarView(QWidget *parent = nullptr):QWidget(parent){
auto *vLayout = new QVBoxLayout;
vLayout->setContentsMargins(0,0,0,0);
vLayout->setSpacing(0);
setLayout(vLayout);
}
};
按照c++的内存管理逻辑,vLayout需要声明为类成员变量,才能保证布局生效,同时需要在析构函数中调用delete释放才不会有内存泄漏,但如果每一个变量都这样会写很多无用代码。
如上:setLayout(vLayout)自动将vLayout添加到当前对象的子对象列表中了,所以它的声明周期和当前对象一样。
createButton同时被vLayout和当前对象添加到子对象列表中
四、智能指针内存管理
1、初步使用
c++11之后引入weak_ptr、shared_ptr。Qt也有QWeakPointer、QSharedPointer与之对应,他们两实现原理及使用方法一样,具体参考(c++语言基础 )
class EditorToolBarView : public QWidget{
public:
EditorToolBarView(QWidget *parent = nullptr):QWidget(parent){
// 智能指针;布局不会生效,因为离开作用域之后vlayout被释放了
vlayout = QSharedPointer<QVBoxLayout>(new QVBoxLayout);
// vlayout = QSharedPointer<QVBoxLayout>::create();
vLayout->setContentsMargins(0,0,0,0);
vLayout->setSpacing(0);
vlayout->setAlignment(Qt::AlignHCenter);
setLayout(vlayout.get());
}
private:
QSharedPointer<QVBoxLayout> vlayout
};
这样的话vlayout对象会随着EditorToolBarView的释放被自动释放
错误使用:
class EditorToolBarView : public QWidget{
public:
EditorToolBarView(QWidget *parent = nullptr):QWidget(parent){
// 智能指针;布局不会生效,因为离开作用域之后vlayout被释放了
QSharedPointer<QVBoxLayout> vlayout(new QVBoxLayout);
vLayout->setContentsMargins(0,0,0,0);
vLayout->setSpacing(0);
setLayout(vlayout.get());
}
};
布局不会生效了,因为vlayout离开作用域后就被智能指针自动释放了。
对于只是在类中使用一次的变量,还是用基于QObject的子对象列表内存管理更好一些。
2、QPixmap的内存管理
先看一段代码:
auto *presetButton = new QPushButton;
auto *presetIcon= new QPixmap(":/preset_selected.png");
presetButton->setIcon(QIcon(*presetIcon));
delete presetIcon;
// QPixmap presetIcon(":/preset_selected.png");
// presetButton->setIcon(QIcon(presetIcon));
即使在调用setIcon()后,presetIcon都被释放了,但是设置按钮图片依然有效。在QPixmap内部有一个智能指针data对应着具体的图片内存。当调用setIcon()方法时,通过拷贝构造函数构造一个新的QPixmap对象,且将data引用计数+1,所以即使外面释放了presetIcon,data也依然被QPushButton持有,同时data的生命周期和QPushButton也一致了。
总结:QWidget框架内部通过子对象列表及智能指针来进行内存管理