因为程序设计错误有可能导致内存泄露.
例如: 以下代码中,当调用division函数抛出异常时,执行流直接跳到了main函数中的catch进行捕捉,此时,此时,new出来的数组array未能及时释放而造成内存泄露.
double division(int a, int b)
{
// 当b == 0时抛出异常
if (b == 0)
{
throw "division by zero condition!";
}
return (double)a / (double)b;
}
void func()
{
// 这里可以看到如果发生除0错误抛出异常,另外下面的array没有得到释放。
int* array = new int[10];
int len, time;
cin >> len >> time;
cout << division(len, time) << endl;
delete[] array;
// ...
cout << "delete []" << array << endl;
delete[] array;
}
int main()
{
try
{
func();
}
catch (exception& e)
{
cout << e.what() << endl;
}
return 0;
}
这里,我们可以通过抛异常的方式处理,调用divison函数时便对Division函数进行捕捉,捕捉时并不对该异常处理,而是先将Array处理,再重新抛出,让main函数中的catch捕捉处理.
double division(int a, int b)
{
// 当b == 0时抛出异常
if (b == 0)
{
throw "division by zero condition!";
}
return (double)a / (double)b;
}
void func()
{
// 这里捕获异常后并不处理异常,而是先释放new出来的指针,异常还是交给外面处理,这里捕获了再重新抛出去。
int* array = new int[10];
try
{
int len, time;
cin >> len >> time;
cout << division(len, time) << endl;
}
catch (...)
{
cout << "delete []" << array << endl;
delete[] array;
throw;
}
// ...
cout << "delete []" << array << endl;
delete[] array;
}
但是如果new出多个指针,每个指针都有可能都可能抛异常导致前面成功创建的指针不能成功释放,如果都使用抛异常处理未免过于繁琐,所以,智能指针油然而生.
RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术.
RAII有两大好处:
实现智能指针有三个主要特征:
如何理解智能指针中的拷贝问题?
对于STL中的迭代器来说,浅拷贝对程序没什么问题,因为迭代器不参与管内存资源的释放,但是对于智能指来说,由于RAII机制,如果只是单纯的浅拷贝,那么在函数栈帧销毁时sp1和sp2会同时调用析构函数对同一块资源进行析构,这会造成程序崩溃.
auto_ptr的基本原理
auto_ptr会将资源管理权进行转移,这将导致被拷贝对象悬空(变为空指针),很多公司明确不能使用auto_ptr.
如图:当ap1拷贝构造ap2时,ap2指向原来ap1管理的内存资源,ap1悬空.
auto_ptr的基本模拟实现
namespace yzh
{
template < class T>
class auto_ptr
{
public:
//构造.
auto_ptr(T* ptr = nullptr)
:_ptr(ptr)
{}
//拷贝构造
auto_ptr(auto_ptr<T>& ap)
:_ptr(ap._ptr) //将管理资源转移.
{
cout << "拷贝构造" << endl;
ap._ptr = nullptr; //将被拷贝指针悬空.
}
//析构
~auto_ptr()
{
if (_ptr)
{
cout << "~auto_ptr()" << endl;
delete _ptr;
}
}
// ->操作符重载.
T* operator->()
{
return _ptr;
}
// * 操作符重载.
T& operator*()
{
return *_ptr;
}
private:
T* _ptr;
};
auto_ptr的打赋值重载
auto_ptr1<T>& operator=(auto_ptr1<T>& ap)
{
if (&ap != this) //对自己给自己赋值进行判断.
{
if (_ptr)
{
cout << "delete _ptr " << endl;
delete _ptr;
}
_ptr = ap._ptr;
ap._ptr = nullptr;
return *this;
}
}
注意:
对于auto_ptr来讲,如果自己给自己赋值,由于赋值时会将原先的管理的资源销毁,并将指针置为空,这显然并不是用户所希望看到的,所以在赋值之前必须先进行判断将该情况排除.
unique_ptr的基本原理
unnique_ptr就是在auto_ptr的基础上不写拷贝构造和赋值重载,再利用delete关键字可防止编译器默认生成拷贝构造和赋值重载.可以简单而粗暴的防拷贝.
unique_ptr(unique_ptr<T>& ap) = delete;
unique_ptr<T>& operator=(unique_ptr<T>& ap) = delete;
shared_ptr的基本原理
通过引用计数的方式实现来实现多个shared_ptr对象共享资源.
那么该引用计数类型可以为静态变量嘛?
shared_ptr的基本实现
namespace yzh
{
template < class T>
class shared_ptr
{
public:
//构造
shared_ptr(T* ptr = nullptr)
:_ptr(ptr)
, _pCount(new int(1)) //刚创建的引用计数值为1.
{}
//拷贝构造
shared_ptr(shared_ptr<T>& sp)
:_ptr(sp._ptr)
, _pCount(sp._pCount)
{
(*_pCount)++; //拷贝构造引用计数加1.
}
~shared_ptr() //智能指针销毁自动调用析构函数
{
if (--(*_pCount) == 0) //当引用计数值为0时,说明该指针为最后一个指向目标资源的对象,可以对该
//资源销毁.
{
if (_ptr != nullptr)
{
cout << "~shared_ptr()" << endl;
delete _ptr;
_ptr = nullptr;
}
delete _pCount;
_pCount = nullptr;
}
}
int* get_cPount()
{
return _pCount;
}
T* get()
{
return _ptr;
}
//像指针一样.
T* operator->()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
// private:
T* _ptr;
int* _pCount;
};
}
赋值重载的基本实现
预期实现:
shared_ptr<T>& operator=(shared_ptr& sp)
{
if (--(*_pcount == 0))
{
delete _ptr;
delete _pcount;
}
_ptr = sp._ptr;
_pcount = sp._pcount;
return *this;
}
在实现前,我们要考虑两种情况.
自己给自己赋值.(同一个对象赋值),此时由于引用计数由1变0,此时p1指向的内存资源A和引用计数_pCount都已经被销毁了,这显然不符合赋值重载的理念.
sp1和sp2两个对象的智能指针赋值,但是它们指向同一块内存资源,此时相当于只对(*_pCount)–后,再++,并没有什么实质性的改变.
所以,我们不能只针对情况一,判断两个智能指针是否为同一对象.我们要根据两个情况的共性来判断,不管为哪种情况,赋值的两个指针指针一定是在指向同一块内存资源A的条件下,所以,如果指向同一块内存资源,则不需要赋值,直接返回.
shared_ptr<T>& operator=(shared_ptr& sp)
{
if (_ptr == sp._ptr) //如果指向相同资源就不做处理.
{
return *this;
}
if (--( * _pCount) == 0 ) //如果引用计数为0,要先将目标资源销毁.
{
delete _ptr;
delete _pCount;
}
_ptr = sp._ptr;
_pCount = sp._pCount;
return *this;
}
我们知道main函数结束之后内存还是要释放的,但是在函数栈帧结束之前还是要尽可能的释放内存,因为很多项目都是长期运行的,防止有可能造成僵尸内存.
可是如果n1中的_next指向结点2,n2中的_prev指向结点1时,当函数栈帧结束的时候,n2先析构,n1后析构,引用计数变为1.
此时便会面临尴尬的情况:
struct Node
{
int _n1;
int _n2;
yzh::shared_ptr<Node> _prev;
yzh::shared_ptr<Node> _next;
~Node()
{
cout << "~Node()" << endl;
}
};
void test_shared_ptr()
{
yzh::shared_ptr<Node> n1(new Node);
yzh::shared_ptr<Node> n2(new Node);
n1->_next = n2;
n2->_prev = n1;
}
int main()
{
//test_auto_ptr();
test_shared_ptr();
return 0;
}
在C++11中,为了解决shared_ptr所造成的循环引用问题,特地引入了weak_ptr,weak_ptr没有引用计数,并且不参与资源的管理.
weak_ptr的基本实现
template < class T >
class weak_ptr
{
public:
//无参构造.
weak_ptr()
:_ptr(nullptr)
{}
//shared_ptr的拷贝构造weak_ptr
weak_ptr(const shared_ptr<T>& sp)
:_ptr(sp.get())
{}
weak_ptr<T>& operator=( shared_ptr<T>& sp)
{
_ptr = sp.get();
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
}
测试代码如下:
此时n1(智能指针)指向第一个结点,n2指向后一个结点,但是结点中的_prev,_next由原先的(shared_ptr转变为weak_ptr),而又因为weak_ptr不参与资源的管理,不决定结点的销毁,所以这时就不会造成两个结点之间的销毁由对方结点是否先销毁而决定的情况,由于结点2比结点1先创建,在函数栈帧销毁的过程中,结点2比结点1先销毁.
int main()
{
//yzh::shared_ptr sp1(new Node);
yzh::shared_ptr<Node> n1(new Node);
yzh::shared_ptr<Node> n2(new Node);
n1->_next = n2;
n2->_prev = n1;
}
new和delete类型不匹配导致的问题
在visual编译器的条件下,如果new方式和delete方式不匹配,对于内置类型没有影响,对于自定义类型会导致程序的崩溃.
int main()
{
std::shared_ptr<Node> n1(new Node);
std::shared_ptr<Node> n2(new Node);
//对于内置类型,new[]类型使用delete没有问题.
std::shared_ptr<int> n3(new int[5]);
//对于自定义类型Node,new[]类型使用delete会导致程序异常.
std::shared_ptr<Node> n4(new Node[5]);
}
new和delete类型不匹配造成程序崩溃的原因
删除定制器的解决办法
一:传递特定的仿函数.
//传仿函数定制删除器
template <class T>
struct DeleteArray
{
void operator()(T* ptr)
{
cout << "delete[]" << endl;
delete[] ptr;
}
};
template <class T>
struct Free
{
void operator()(T* ptr)
{
cout << "delete[]" << endl;
free(ptr);
}
};
int main()
{
//调用析构函数,delete.
std::shared_ptr<Node> n1(new Node);
//调用free.
std::shared_ptr<int> n2(new int[5], Free<int>());
//仿函数,delete[].
std::shared_ptr<Node> n3(new Node[5], DeleteArray<Node>());
std::shared_ptr<int> n4((int*)malloc(sizeof(5)),Free<int>());
}
二: 传lambda表达式
int main()
{
//调用析构函数,delete.
std::shared_ptr<Node> n1(new Node);
//调用free.
std::shared_ptr<int> n2(new int[5], [](int* ptr) { free(ptr); });
//仿函数,delete[].
std::shared_ptr<Node> n3(new Node[5], [](Node* ptr) { delete[] ptr; });
std::shared_ptr<int> n4((int*)malloc(sizeof(5)), [](int* ptr) {free(ptr); });
}