内存泄漏
- 什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
- 内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。
在下面这段代码中,运行main函数时先执行Func函数,Func函数先new了一个数组,再调用执行Division函数
若执行Division函数时发生异常,则会直接跳转到catch处去捕获异常,这时在Func中new的数组并没有delete
这就造成了内存泄漏问题
double Division(int a, int b)
{
// 当b == 0时抛出异常;异常抛出后就必须进行捕获
if (b == 0)
throw "除数不能为0"; // 抛出的异常可以是任意类型的对象,此处为常量字符串指针
else
return (a / b);
}
void Func()
{
int* array = new int[10]; // new一个数组
int a, b;
cin >> a >> b;
cout << Division(a, b) << endl;
delete[] array; // delete数组
}
int main()
{
try {
Func();
}
catch (const char* errmsg) // 捕获抛出的字符串指针
{
cout << errmsg << endl; // 对其及进行输出
}
return 0;
}
那有没有什么好的方法来解决呢?使用智能指针可以避免内存泄漏
智能指针原理和简单实现
- 智能指针在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。实际上时把管理一份资源的责任托管给了一个对象。即RAII(Resource Acquisition Is Initialization–资源获取即初始化),一种利用对象生命周期来控制程序资源的技术
- 智能指针同时也要能像指针一样去使用它
使用智能指针解决文章前面遇到的内存泄漏问题:
template <class T>
class SmartPtr
{
public:
SmartPtr(T* ptr):_ptr(ptr)
{}
~SmartPtr()
{
if(_ptr)
{
delete _ptr;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
void Test()
{
int* p = new int[10];
SmartPtr<int> sp(p); // 使用智能指针把p对象保存起来
throw exception(); // 异常
//delete[]p; // 这里不需要再去手动释放,p出作用域时,会自动调用智能指针的析构函数将p释放
}
int main()
{
try
{
Test();
}
catch (exception& e)
{
cout << e.what() << endl;
}
catch (...)
{
cout << "未知异常" << endl;
}
return 0;
}
在上面的代码中,我们简单地实现了智能指针,但是如果要拷贝构造一个智能指针对象时,就会出现问题:
int main()
{
SmartPtr<int> sp1(new int); // 使用智能指针把new出来的整形保存起来
SmartPtr<int> sp2(sp1); // 使用sp1拷贝构造一个sp2
return 0;
}
如果需要使用拷贝构造来创建一个智能指针对象,这时新构造的对象会和原对象指向同一块内存空间,在出了作用域后进行销毁时,这个对象会被销毁两次,这样就会发生浅拷贝问题
那么,能否自己写一个深拷贝的拷贝构造和拷贝赋值函数来解决浅拷贝问题呢?
自己写一个深拷贝构造函数固然可以解决上面出现的浅拷贝问题,可这样就背离了智能指针的初衷。
首先,智能指针里存放的对象不是属于智能指针的,智能指针的作用只是托管这个对象,不应有权利去深拷贝这个对象资源
其次,智能指针要有像指针一样的行为,而把一个指针赋给另一个指针,它是不会拷贝原指针所指向的内容的
所以,不能简单地对拷贝构造函数和赋值函数进行改写来解决问题
那么,有什么好的方法可以解决这个问题呢?
答案当然是:前人早已经想到了这些问题并且帮我们解决了,从智能指针的发展历史中也可以看出设计思路的变化
智能指针发展历史
C++98中的auto_ptr: 一旦发生拷贝,就将原对象中资源转移到当前对象中,然后另原对象与其所管理资源断开联系并指向空,这样就解决了一块空间被多个对象重复释放而造成程序奔溃问题
但是,这种管理权转移的方式,会带来悬空问题,导致访问时发生崩溃:原对象的资源已经指向了新的对象,这时不小心访问了原对象并对它进行修改操作,就会导致程序崩溃
为了解决这个问题,又有了unique_ptr
C++11中的unique_ptr: unique_ptr比较简单粗暴,直接使用防拷贝的方法禁止以拷贝构造的方式来创建智能指针对象
这里介绍两种防拷贝的方法:
// C++98防拷贝的方式:只声明不实现并且声明为私有,这样即使有人在类外实现了也无法调用
private:
UniquePtr(UniquePtr<T> const &);
UniquePtr & operator=(UniquePtr<T> const &);
// C++11防拷贝的方式:delete
UniquePtr(UniquePtr<T> const &) = delete;
UniquePtr & operator=(UniquePtr<T> const &) = delete;
这种方式虽然解决了资源重复释放的问题,但不能拷贝构造智能指针对象,在有些场景下又不能满足使用需求
那有没有一种既能防止资源重复释放,又可以用拷贝构造的方式来创建智能指针对象呢?当然有!这就又引出了shared_ptr
C++11中的shared_ptr 使用引用计数的方式实现拷贝构造
shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。若要使用原对象的资源进行拷贝构造新的对象,只需把这份资源的引用计数+1;在对象被销毁时(也就是析构函数调用),对象的引用计数-1;若引用计数不是0,就说明除了自己还有其它对象在使用该份资源,不能释放该资源;若引用计数变为0,就说明是最后一个使用该资源的对象,对该资源进行释放
引用计数需要维护在堆上,每申请一份独立的资源,就创建一个引用计数对象,在资源释放时引用计数也需要释放