一 概述
要避免上节出现的内存泄漏,我们就不能用按值的方式来返回一个堆中对象,而必须按地址或者别名的方式返回一个别名或者内存地址,这样就不会调用复制构造函数创建一个该对象的副本,而是直接将该对象的别名或者地址返回。由于返回的对象的别名或者地址初始化给了main函数中的一个引用或者指针,因此即使被调用函数中的局部指针超出作用域被系统释放,也可由main函数中的引用或者指针找到该堆中空间,不会令该空间成为不可访问的区域,从而避免了内存泄漏
二 上节错误的修正
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 *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; cout<<r.get()<<endl; return 0; }
|
2.2 输出结果
1 2 3 4 5 6
| 跳转到func函数中! 执行构造函数创建一个对象 对象a的地址:0x3378d0 对象a的副本的地址:0x3378d0 99 执行析构函数!
|
2.3 代码说明
- 我们再来看一下这个堆中对象的地址,和从func函数返回的对象的地址,我们看到这两个地址都是相同的
- 由于它们的地址相同,所以即使func函数中的局部指针p被系统自动销毁了,我们也能通过main函数中的别名r来访问到堆中对象
- 因为r与堆中对象的地址是相同 ,r是堆中对象的别名,所以对r的操作就是对堆中对象的操作
- 由于无法对引用使用delete运算符,因此我们只能定义一个指针来存储引用的地址,然后删除该指针指向的内存空间
- "执行析构函数"证明析构函数被调用,同时证明堆中对象已经被销毁
- 这一切操作都非常顺利,程序也输出了正确的结果。但是这个程序却隐藏着一个非常严重的问题:由于p所指向的堆中对象被删除了,因此堆中对象的别名r成了空别名。A *p=&r中,r是一个不存在的对象的别名,因此假如再使用这个空别名去访问不存在 的对象的成员函数get()时,由于r所引起的对象已经不存在,所以再次访问该对象的X成员时,输出一个随机数,这样就导致了一个不宜觉察的错误,这个错误不易被编译器捕获到,也不会令程序奔溃,所以要检测到这个错误很困难