- A+
问题
问题:现有一个成员变量QList,在函数中定义了一个局部变量的对象,并将该局部变量加入到QList中。当函数运行结束,局部变量也就出了作用域,这时,由于局部变量只是一个普通对象,而不是指针,所以,应该被销毁。但是,我们在外面仍然可以通过QList对象来访问之前加入进来的数据,这是为什么?
实验
#include <QApplication> #include "mainwindow.h" #include <QDebug> #include <QLabel> #include <QString> struct tagTEST { int a; QLabel *x; }; QList testFunc() { QList testList; tagTEST tmp; qDebug()<<"before add list:"; for(int i=0; i<10; i++) { tmp.a = i; tmp.x = new QLabel(QString::number(i)); testList.append(tmp); qDebug()<<i<<" a value:"<<tmp.a<<" x value:"<<tmp.x; } qDebug()<<"before add list size:"<<testList.size(); return testList; } int main(int argc,char *argv[]) { QFont f("ZYSong18030",10); QApplication::setFont(f); QApplication app(argc,argv); QList list = testFunc(); int size = list.size(); int i; qDebug()<<"after add list:"; qDebug()<<"after add list size:"<<size; for(i=0; i<size; i++) { qDebug()<<i<<" a value:"<<list[i].a<<" x value:"<<list[i].x; } MainWindow *mainwindow = new MainWindow(); mainwindow->show(); return app.exec(); }
运行结果
before add list: 0 a value: 0 x value: QLabel(0x3cad0) 1 a value: 1 x value: QLabel(0x3cd30) 2 a value: 2 x value: QLabel(0x3cbf0) 3 a value: 3 x value: QLabel(0x3cb90) 4 a value: 4 x value: QLabel(0x3ccf0) 5 a value: 5 x value: QLabel(0x3cd10) 6 a value: 6 x value: QLabel(0x3cd90) 7 a value: 7 x value: QLabel(0x3ca90) 8 a value: 8 x value: QLabel(0x3cdd0) 9 a value: 9 x value: QLabel(0x3cc90) before add list size: 10 after add list: after add list size: 10 0 a value: 0 x value: QLabel(0x3cad0) 1 a value: 1 x value: QLabel(0x3cd30) 2 a value: 2 x value: QLabel(0x3cbf0) 3 a value: 3 x value: QLabel(0x3cb90) 4 a value: 4 x value: QLabel(0x3ccf0) 5 a value: 5 x value: QLabel(0x3cd10) 6 a value: 6 x value: QLabel(0x3cd90) 7 a value: 7 x value: QLabel(0x3ca90) 8 a value: 8 x value: QLabel(0x3cdd0) 9 a value: 9 x value: QLabel(0x3cc90)
得出结论
说明append函数在内部进行了数据的复制,并且这种复制只是浅拷贝,因为list中每个元素的x指针和tmp中的指针地址一样,说明只是进行了浅拷贝。
图中,从刚开始,在函数中创建tmp元素时,分别在堆和栈上分别创建了数据,加入list之后,将原来tmp中的在栈上面的元素进行了浅拷贝,x指向的依旧是tmp中的那个内存,但是,元素添加进list之后,元素的作用域扩大了,因此,当函数运行结束后,tmp被销毁,但tmp所指向的内存空间依旧在,我们依然在外面通过list访问中的数据
2018年10月11日 下午11:11 沙发
首先这个文章中的结论是错误的.
其次文章代码有两处主要错诶,我发表个人意见指正一下.
错误一:指针变量不初始化,造成程序崩溃隐患;
错误二是c++ 的new 和 delete 的调用次数一定要对应,可是看上文的代码只有new 没有 delete,这是出现了内存泄露!
可以适当修改代码如下:
class tagTEST
{
public:
int a = 0;
QLabel *x = nullptr;
tagTEST(){}
~tagTEST(){ if(x) delete x; }
};
再去运行代码看看,你会发现结论完全变了,你将得到这样的输出:
before add list:
0 a value: 0 x value: QLabel(0x3cad0)
1 a value: 1 x value: QLabel(0x3cd30)
2 a value: 2 x value: QLabel(0x3cbf0)
3 a value: 3 x value: QLabel(0x3cb90)
4 a value: 4 x value: QLabel(0x3ccf0)
5 a value: 5 x value: QLabel(0x3cd10)
6 a value: 6 x value: QLabel(0x3cd90)
7 a value: 7 x value: QLabel(0x3ca90)
8 a value: 8 x value: QLabel(0x3cdd0)
9 a value: 9 x value: QLabel(0x3cc90)
before add list size: 10
after add list:
after add list size: 10
程序异常结束。
2018年10月11日 下午11:32 1层
@RenShen 我学qt也不久,你改了代码,结果运行出错,是因为以下两点原因,并不能证明文中结论是错误的
1、tagTEST tmp只是一个局部变量,出了作用域之后,tmp会被浅拷贝出一个新对象加入到list中,tmp会被析构掉,调用它的析构函数,同时释放掉tmp中x所指向的内存空间,但因为是浅拷贝,所以,list中每个tagTEST元素中x所指向的内存空间其实是null,所以程序崩溃了
2、 QLabel继承自QObject,由Qt自动管理内存,不要随便自己手动释放QObject对象
2018年10月11日 下午11:41 2层
@阿拉灯aladeng 看来我说的不够明显,这里你的Qt知识中再一次出现了一点误区,Qt自动管理内存是基于父子关系的, 并不是说QObject对象不需要手动释放.
只是说,当对象B的父对象指定为A对象时,此时B对象的内存管理才会由A代为管理,当A析构时,遵循先子后父的顺序,A会将B先释放.
你的代码是出现了内存泄露,所以才能运行,并不是说QList 将tagTEST tmp局部变量的生命周期扩大了,只是你的X指针泄露了
2018年10月11日 下午11:57 3层
@RenShen 你说的确实有道理,qlabel必须加入到父容器中才能将其交由父容器代为管理,但问题来了,这并不能证明文中的结论是错的(文中“元素的作用域扩大了”说法是个生动的描述,并不是真的扩大了生命周期),因为确实是浅拷贝,才会出现你程序中的异常退出,如果真照你上面的写法,那qlist中会存在很多为null的内存……而且,这里其实也算不上内存泄漏,因为内存还在管控中,只是由原来的temp交由list来管理了
2018年10月11日 下午11:58 3层
@RenShen 其实我也很想知道,如果我这样写不对,而你那样写又会崩溃,那应该怎么写呢
2018年10月12日 上午12:27 4层
@阿拉灯aladeng 回到这片文章的提问,可以的解释就是,tagTEST tmp局部变量中的数据,由QList中的另外一个tagTEST拷贝了,并且在tagTEST tmp局部变量退出作用域后,他的成员x指向的内存并没有随之释放,所以通过另外一个tagTEST对象仍然可以访问到x指向的内存区域.与QList并没有可以扩大作tagTEST tmp局部变量用域的能力.
这里为什么说发生了内存泄露,因为在退出main函数的作用域后,x指向的内存依旧没有被释放,要明白,main函数也是被其他系统接口调用的,程序层面上就造成了野指针.
总之就是你对这段代码能运行分析得到的结论有误,代码本来就是有错的,没改的必要.
但是问如何能避免此处 QLable对象的管理问题,除了指定父对象之外,对于没有父对象的QObject对象还能用Qt 或者 std 的智能指针管理堆变量.
Qt中提供了QPointer、QScopedPointer、QSharedPointer、QWeakPointer等几种基本智能指针,可以学习一下
2018年10月12日 上午12:33 4层
@阿拉灯aladeng Qt 智能指针学习 – 1+1=10 – CSDN博客
https://blog.csdn.net/dbzhang800/article/details/6403285
2018年10月12日 下午8:20 3层
@RenShen 我还是不大明白,你可以基于上面代码做个修改吗,既可以在外面正常访问list中的元素,又可以避免内存泄露