CPP学习之——引用一个按值返回的堆中对象(9.17)

一 概述

借用上一节课的代码。本节课讲的也是一种比较常见的错误 :引用一个返回的堆中对象引起的错误

二 代码及结果输出

2.1 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include<iostream>
using namespace std;
class A
{
private:
int x;
public:
A(int i)
{
cout << "执行构造函数创建一个对象\n";
x = i;
}
A(const A &a)
{
x = a.x;
cout << "执行复制构造函数创建对象\n";
}
~A() {cout << "执行析构函数!\n";}
int get() const {return x;}
};
A func()
{
cout << "跳转到func函数中!\n";
//A a(23);
A *p=new A(99);
cout << "对象a的地址:" << p << endl;
return *p;
}
int main()
{
const A&r=func();
cout << "对象a的副本的地址:" << &r << endl;
cout << r.get() << endl;
const A *p=&r;
delete p;
return 0;
}

3.2 结果及现象

  • 程序异常

3.3 代码说明

  • 输出结果只执行了一次析构函数:p指针被删除了,它所指向的空间还存在。该空间的地址只有p保存着,而p找不到了,所以我们无法找到该空间。由于无法找到该空间,所以无法对其进行释放,结果造成了内存泄漏
  • 由于引用会延长临时变量的生命,所以当main函数结束时,由复制构造函数创建的堆中对象的副本方才被销毁,所以调用析构函数来销毁该对象占用的内存,由于堆中对象无法找到,无法对其占用的内存进行释放,所以只调用了一次析构函数,这个析构函数销毁的是堆中对象的副本。
  • 由于返回的是堆中对象的副本,这个副本是调用复制构造函数创建的。所以这个副本保存在栈中,而不是堆中。我们知道存放在栈中的数据都是由系统自动释放,而delete运算符删除的是堆中空间,而不是栈中空间,所以导致了错误
  • 另外需要再次强调的是,指向堆中空间的p指针由于是个局部变量,因此在func函数返回时,它被系统自动释放了,然后由于它所指向的堆中空间必须使用delete运算符才能被删除,因此该空间成了不可访问的区域,结果导致了内存泄漏