注:博客中内容主要来自《狄泰软件学院》,博客仅当私人笔记使用。
测试环境:Ubuntu 10.10
GCC版本:9.2.0
一、永恒的话题
1)内存泄露(臭名昭著的Bug)
- 动态申请堆空间,用完后不归还
- C++语言中没有垃圾回收的机制
- 指针无法控制所指堆空间的生命周期
编程实验
内存泄露
37-1.cpp
#include <iostream>
#include <string>
using namespace std;
class Test
{
int i;
public:
Test(int i)
{
this->i = i; //不用this区分不出来是哪个i
}
int value()
{
return i;
}
~Test()
{
}
};
int main()
{
for(int i=0; i<5; i++)
{
Test* p = new Test(i); //没有释放申请的内存p
cout << p->value() << endl;
}
return 0;
}
操作:
1) g++ 37-1.cpp -o 37-1.out编译正常,打印结果:
0
1
2
3
4
分析:
for循环内定义局部对象指针p,申请堆内存,每次循环结束后没有释放内存,发生内存泄露,如果进程长期运行,可使用的内存越来越小,系统运行越来越慢。
二、深度的思考
1)我们需要什么
- 需要一个特殊的指针
- 指针生命周期结束时主动释放堆空间(特别需要)
- 一片堆空间最多只能由一个指针标识(对象间赋值时,防止浅拷贝带来重复释放堆内存的危害。)
- 杜绝指针运算和指针比较(避免指针越界和野指针)
三、智能指针分析
1)解决方案
- 重载指针特征操作符(->和*)
- 只能通过类的成员函数重载
- 重载函数不能使用参数
- 只能定义一个重载函数
智能指针本质:是个类,对象创建时,指针产生,分配内存;对象被摧毁时,调用析构函数,释放内存。
编程实验
智能指针
37-2.cpp
#include <iostream>
#include <string>
using namespace std;
class Test
{
int i;
public:
Test(int i)
{
cout << "Test(int i)" << endl;
this->i = i;
}
int value()
{
return i;
}
~Test()
{
cout << "~Test()" << endl;
}
};
class Pointer
{
Test* mp;
public:
Pointer(Test* p = NULL) //初始化指针
{
cout << "Pointer(Test* p = NULL)" <<endl;
mp = p;
}
Pointer(const Pointer& obj)
{
mp = obj.mp;//将被使用的类的堆空间,给当前类的指针,为了避免冲突
const_cast<Pointer&>(obj).mp = NULL;//销毁obj对象中的指针(实现:一片堆空间只有一个指针)
}
Pointer& operator = (const Pointer& obj)
{
if( this != &obj ) //取对象地址(效率高)
{
delete mp; //从异常安全性原则考虑定义临时变量交换,退出代码调用析构函数释放赋值前当前对象中的数据
mp = obj.mp;
const_cast<Pointer&>(obj).mp = NULL;
}
//cout << "b" <<endl
return *this;
}
/* 重载-> :操作指针,最好拷贝一份指针,用拷贝的指针进行操作,以免地址丢失*/
Test* operator -> ()
{
return mp;
}
/* 重载* :引用类型为了返回指针指向的变量本身,而不是拷贝值*/
Test& operator * ()
{
return *mp;
}
bool isNull()
{
return (mp == NULL);
}
~Pointer()
{
cout << "~Pointer()" << endl;
delete mp;
}
};
int main()
{
Pointer p1 = new Test(0); //调用Test(int i)和Pointer(Test* p = NULL)
cout << p1->value() << endl; //0
Pointer p2 = p1; //
//上一行等价于Pointer p2(p1);没有调用重载的赋值操作符(=)
cout << p1.isNull() << endl; //1
cout << p2->value() << endl; //0。p2->value() ==> mp->value
return 0;
}
操作:
1) g++ 37-2.cpp -o 37-2.out编译正常,打印结果:
Test(int i) ==> new Test(0)
Pointer(Test* p = NULL) ==> p1构造函数
0
1
0
~Pointer() ==> p2析构函数
~Test()
~Pointer() ==> p1析构函数
分析:
智能指针实现存储一个目标类型的类指针变量,构造函数中指针指向目标对象(Test)数据堆内存。智能指针销毁时,调用析构函数,析构函数中delete目标类型指针,然后调用指针堆空间中存储的目标类型对象(Test)进行销毁,进而触发该对象(Test)的析构函数,销毁内存(间接方式销毁内存)。
缺陷:
必须在智能指针中实现定义好目标类型类指针变量,不灵活。
解决办法:
通过泛类型编程(模板技术)解决,支持更多类型!
int main()
{
for(int i=0; i<5; i++)
{
Pointer p = new Test(i);
cout << p->value() << endl;
}
}
智能指针类对象p是在栈上定义,并分配内存,每循环一次都会退出代码段,栈上的p被销毁,
智能指针类中的析构函数被触发,去释放申请的堆内存,防止指针泄露。
2) 智能指针的使用军规
只能使用指向堆空间中的对象或者变量
不要使用智能指针指向栈空间中的地址!(不用C格式的指针,{}中的都是栈空间)
小结
1)指针特征操作符(->和*)可以被重载
2)重载指针特征符能够使用对象代替指针
3)智能指针只能用于指向堆空间中的内存
4)智能指针的意义在于最大程度的避免内存错误