内存泄露(臭名昭著的Bug)
-动态申请堆空间,用完后不归还
-C++语言中没有垃圾回收的机制(java、C#有垃圾回收机制)
-指针无法控制所指堆空间的生命周期
#include <iostream>
#include <string>
using namespace std;
class Test
{
int i;
public:
Test(int i)
{
this->i = i;
}
int value()
{
return i;
}
~Test()
{
}
};
int main()
{
for(int i=0; i<5; i++)
{
Test* p = new Test(i);
cout << p->value() << endl;
}
return 0;
}
这个程序会造成内存的泄露,你指申请了堆空间,但是没有释放。p是一个局部变量,在for循环结束之后就消失了,但是它指的堆空间并没有消失,你无法使用这片堆空间了。
深度的思考
我们需要什么
-需要一个特殊的指针
-指针生命周期结束时主动释放堆空间
-一片堆空间最多只能由一个指针标识。(可以避免多次释放的问题,如果两个指针指向了一片堆空间,那么很可能该空间被释放两次,)
-杜绝指针运算和指针比较(避免指针越界和野指针)
解决方案(通过一个对象来模拟指针)
-重载指针特征操作符(->和*)
-只能通过类的成员函数重载
-重载函数不能使用参数
-只能定义一个重载函数
使用对象替代指针后,这个对象就拥有了一个高端大气上档次的名字,智能指针。
#include <iostream>
#include <string>
using namespace std;
class Test
{
private:
int i;
public:
Test(int i)
{
cout << "Test(int i)" << endl;
this->i = i;
}
int value()
{
return i;
}
~Test()
{
cout << "Test(int i)" << endl;
}
};
class Pointer
{
private:
Test* mp;
public:
Pointer(Test*p = NULL)
{
mp = p;
}
Test* operator ->()
{
return mp;
}
Test& operator *()
{
return *mp;
}
};
int main()
{
for(int i=0; i<5; i++)
{
//Test* p = new Test(i);
Pointer p = new Test(i);
cout << p->value() << endl;
}
return 0;
}
从运行结果看,该智能指针并没有自动调用析构函数来释放堆空间。
在Pointer类中加上析构函数,再次运行:
此时析构函数被调用,解决了内存泄露的问题。
再次看我们的需求,前两条已经被实现:
-需要一个特殊的指针(特殊的指针就是一个对象,通过一个对象来代替指针,通过一个对象来模拟指针的行为)
-指针生命周期结束时主动释放堆空间
-一片堆空间最多只能由一个指针标识。(可以避免多次释放的问题,如果两个指针指向了一片堆空间,那么很可能该空间被释放两次,)
-杜绝指针运算和指针比较(避免指针越界和野指针)
#include <iostream>
#include <string>
using namespace std;
class Test
{
private:
int i;
public:
Test(int i)
{
cout << "Test(int i)" << endl;
this->i = i;
}
int value()
{
return i;
}
~Test()
{
cout << "~Test" << endl;
}
};
class Pointer
{
private:
Test* mp;
public:
Pointer(Test*p = NULL)
{
mp = p;
}
Pointer(const Pointer& obj)
{
//delete mp;这个地方一定不要加上这句,因为这是在拷贝构造函数中,mp还不存在,它是一个野指针。不可能去删除一个野指针。
mp = obj.mp; //当前对象的成员指针也指向了初始化对象的成员指针所对应的堆空间,此时意味着两个指针指向了同一片堆空间。
const_cast<Pointer&>(obj).mp = NULL;//将初始化对象的成员指针所指向的堆空间指为空,意味着初始化对象将它所指向的堆空间完全交给了当前对象,实际上是一个所有权的传递。
//obj.mp = NULL; 因为obj是const的,不能这样直接赋值。需要利用const_cast将初始化对象的只读属性去掉。
}
Pointer& operator = (const Pointer& obj)
{
if(this != &obj)
{
delete mp; //这个地方为什么可以,因为这是赋值操作符的重载,体现在赋值上,说明当前对象已经存在了,mp可能指向了某个内存空间。
mp = obj.mp;
//obj.mp = NULL;
const_cast<Pointer&>(obj).mp = NULL;
}
return *this;
}
Test* operator ->()
{
return mp;
}
Test& operator *()
{
return *mp;
}
bool isNULL()
{
return (mp == NULL);
}
~Pointer()
{
delete mp;
}
};
int main()
{
Pointer p1 = new Test(0);
cout << p1->value() << endl;
Pointer p2 = p1;
cout << p1.isNULL() <<endl;
cout << p2->value() << endl;
return 0;
}
在main函数中,再进行指针比较,看看编译能否通过。例如p2++ , if(p1==p2),这样的指针运算是无法编译的。说明了我们已经按照上面的那4条需求实现了一个智能指针。
但是这个智能指针所对应的类仅仅指向Test这个固定的类型,它没有办法指向其他的类型,那能否对其修改,使得Pointer指向各种各样的类型呢?其实是可以的,只不过现在我们还不具备这种技术。这个地方使用模板就可以做到。
智能指针的使用军规:
只能用来指向堆空间中的对象或者变量。
小结:
指针特征操作符(-> 和*)可以被重载
重载指针特征符能够使用对象代替指针
智能指针只能用于指向堆空间中的内存
智能指针的意义在于最大限度的避免内存问题