C++没有垃圾回收机制。
#include
using namespace std;
int div()
{
int a, b;
cin >> a >> b;
if (b == 0)
throw invalid_argument("除0错误");
return a / b;
}
void Func()
{
// 1、如果p1这里new 抛异常会如何?
// 2、如果p2这里new 抛异常会如何?
// 3、如果div调用这里又会抛异常会如何?
int* p1 = new int[10];
int* p2 = nullptr;
int* p3 = nullptr;
try
{
p2 = new int[20];
}
catch (...)
{
delete[] p1;
throw;
}
try
{
p3 = new int[10];
}
catch (...)
{
delete[] p1;
delete[] p2;
throw;
}
try
{
cout << div() << endl;
}
catch (...)
{
delete[] p1;
delete[] p2;
delete[] p3;
throw;
}
delete[] p1;
delete[] p2;
}
int main()
{
try
{
Func();
}
catch (exception& e)
{
cout << e.what() << endl;
}
return 0;
}
在多次new时有可能抛异常,就需要一层一层捕获异常并释放资源还需要条件判断,非常麻烦,否则抛异常之后delete多了或者少了都不行。一不小心就内存泄露了。
1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。ps: 这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智 能指针来管理才有保证。
2. 采用RAII思想或者智能指针来管理资源。
3. 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
4. 出问题了使用内存泄漏工具检测。ps:不过很多工具都不够靠谱,或者收费昂贵。
智能指针指针包含两个部分:RAII(通过构造和析构管理资源)和像指针一样的操作符重载。
智能指针的本质时将资源生命周期与对象绑定。
下面来见一见RAII的智能指针:
template
class smart_ptr
{
public:
smart_ptr(T* ptr)
:_ptr(ptr)
{}
~smart_ptr()
{
delete _ptr;
}
private:
T* _ptr;
};
我们再来实现像指针一样的部分:
template
class smart_ptr
{
public:
//RAII
smart_ptr(T* ptr)
:_ptr(ptr)
{}
~smart_ptr()
{
std::cout << "delete" << _ptr << std::endl;
delete _ptr;
}
//像指针一样
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T& operator[](size_t pos)
{
return _ptr[pos];
}
private:
T* _ptr;
};
int main()
{
szg::smart_ptr sp1(new Date());
cout << sp1->_year << ":" << sp1->_month << ":" << sp1->_day << endl;
++sp1[0]._year;
++(*sp1)._month;
++sp1->_day;
cout << sp1->_year << ":" << sp1->_month << ":" << sp1->_day << endl;
return 0;
}
//0:0 : 0
//1 : 1 : 1
//delete00732778
智能指针的方式也会产生一些问题,比如拷贝构造和赋值会重复析构。
int main()
{
szg::smart_ptr sp1(new Date());
szg::smart_ptr sp2(sp1);
return 0;
}
//delete00CE27C0
//delete00CE27C0
//直接报错
在学习解决这个问题前先了解一下智能指针的发展历史:
// C++智能指针发展历史 // C++98 auto_ptr 资源管理权转移-->对象悬空 很多公司明确要求不能使用它 // boost scoped_ptr 防拷贝 // boost shared_ptr/weak_ptr // 引用计数 // C++11 unique_ptr 防拷贝 // C++11 shared_ptr/weak_ptr // 引用计数
最先提出的是auto_ptr,它是通过资源管理权转移解决重复析构问题的:
template
class auto_ptr
{
public:
//RAII
auto_ptr(T* ptr)
:_ptr(ptr)
{}
~auto_ptr()
{
if (_ptr)
{
std::cout << "delete" << _ptr << std::endl;
delete _ptr;
}
}
auto_ptr(auto_ptr& ap)
:_ptr(nullptr)
{
std::swap(_ptr, ap._ptr);
}
auto_ptr& operator=(auto_ptr& ap) = delete;
//像指针一样
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T& operator[](size_t pos)
{
return _ptr[pos];
}
private:
T* _ptr;
};
貌似解决了问题,但会导致对象悬空,不建议使用。
unique_ptr脱胎于boost库(C++标准库的预备库)的scoped_ptr,通过禁用拷贝的方式解决重复析构问题。
template
class unique_ptr
{
public:
//RAII
unique_ptr(T* ptr)
:_ptr(ptr)
{}
~unique_ptr()
{
if (_ptr)
{
std::cout << "delete" << _ptr << std::endl;
delete _ptr;
}
}
unique_ptr(unique_ptr& ap) = delete;
unique_ptr& operator=(unique_ptr& ap) = delete;
//像指针一样
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T& operator[](size_t pos)
{
return _ptr[pos];
}
private:
T* _ptr;
};
shared_ptr/weak_ptr使用引用计数方式防止拷贝构造。
新开辟一块区域作为引用计数的空间:
template
class shared_ptr
{
public:
//RAII
shared_ptr(T* ptr)
:_ptr(ptr)
, _pcnt(new int(1))
,_pmutex(new std::mutex)
{}
void release()
{
bool flag = false;
_pmutex->lock();
if (--(*_pcnt) == 0 && _ptr)
{
delete _ptr;
delete _pcnt;
flag = true;
}
_pmutex->unlock();
if (flag)
{
delete _pmutex;
}
}
~shared_ptr()
{
release();
}
shared_ptr(const shared_ptr& sp)
:_ptr(sp._ptr)
,_pcnt(sp._pcnt)
,_pmutex(sp._pmutex)
{
_pmutex->lock();
++(*_pcnt);
_pmutex->unlock();
}
shared_ptr& operator=(const shared_ptr& sp)
{
if (_ptr != sp._ptr)
{
release();
_ptr = sp._ptr;
_pcnt = sp._pcnt;
_pmutex = sp._pmutex;
_pmutex->lock();
++(*_pcnt);
_pmutex->unlock();
}
return *this;
}
//像指针一样
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T& operator[](size_t pos)
{
return _ptr[pos];
}
private:
T* _ptr;
int* _pcnt;
std::mutex* _pmutex;
};
int main()
{
szg::shared_ptr sp1(new int(0));
szg::shared_ptr sp2(sp1);
szg::shared_ptr sp3(sp2);
(*sp1)++;
(*sp2)++;
cout << *sp1 << endl;
cout << *sp2 << endl;
sp1 = sp2;
szg::shared_ptr sp4(new int(10));
szg::shared_ptr sp5(sp4);
sp1 = sp4;
return 0;
}
循环引用分析:
1. node1和node2两个智能指针对象指向两个节点,引用计数变成1,我们不需要手动 delete。
2. node1的_next指向node2,node2的_prev指向node1,引用计数变成2。
3. node1和node2析构,引用计数减到1,但是_next还指向下一个节点。但是_prev还指向上 一个节点。
4. 也就是说_next析构了,node2就释放了。
5. 也就是说_prev析构了,node1就释放了。
6. 但是_next属于node的成员,node1释放了,_next才会析构,而node1由_prev管理,_prev 属于node2成员,所以这就叫循环引用,谁也不会释放。
struct ListNode
{
ListNode()
{
val = 0;
}
int val;
szg::shared_ptr _next;
szg::shared_ptr _prev;
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
void test_shared_ptr2()
{
szg::shared_ptr n1(new ListNode);
szg::shared_ptr n2(new ListNode);
n1->_next = n2;
n2->_prev = n1;
cout << n1.use_count() << endl;
cout << n2.use_count() << endl;
}
int main()
{
test_shared_ptr2();
return 0;
}
循环引用的本质是节点内含智能指针,且节点内的智能指针互相引用(指向对方)。
由于计数引用导致了:
因为你中有我,我不能先释放
因为我中有你,你不能先释放
导致都不能释放,直接造成内存泄漏
循环引用是shared_ptr的死穴,要用weak_ptr解决。
weak_ptr解决循环引用是通过使weak_ptr可以指向资源,但是不增加引用计数实现的。不支持管理资源,只用于share_ptr的拷贝,是shared_ptr的小弟。
template
class weak_ptr
{
public:
weak_ptr()
:_ptr(nullptr)
{}
weak_ptr(const shared_ptr& sp)
:_ptr(sp.get())
{}
weak_ptr& operator=(const shared_ptr& sp)
{
_ptr = sp.get();
return *this;
}
// 像指针一样
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
public:
T* _ptr;
};
struct ListNode
{
ListNode()
{
val = 0;
}
int val;
// 可以指向资源/访问资源,不参与资源管理,不增加引用计数
szg::weak_ptr _next;
szg::weak_ptr _prev;
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
void test_shared_ptr2()
{
szg::shared_ptr n1(new ListNode);
szg::shared_ptr n2(new ListNode);
n1->_next = n2;
n2->_prev = n1;
cout << n1.use_count() << endl;
cout << n2.use_count() << endl;
}
int main()
{
test_shared_ptr2();
return 0;
}
定制一个资源释放的方式,毕竟new和new[]出来的资源的释放方式是不一样的,得匹配delete和delete[]。
只要使用包装器对象保存释放资源的方法即可:
template
class shared_ptr
{
public:
//RAII
shared_ptr(T* ptr = nullptr, const std::function func = [](T* t) {delete t;})
:_ptr(ptr)
, _pcnt(new int(1))
,_pmutex(new std::mutex)
{
_func = func;
}
void release()
{
bool flag = false;
_pmutex->lock();
if (--(*_pcnt) == 0 && _ptr)
{
_func(_ptr);
delete _pcnt;
flag = true;
}
_pmutex->unlock();
if (flag)
{
std::cout << "delete" << std::endl;
delete _pmutex;
}
}
~shared_ptr()
{
release();
}
shared_ptr(const shared_ptr& sp)
:_ptr(sp._ptr)
,_pcnt(sp._pcnt)
,_pmutex(sp._pmutex)
{
_func = sp._func;
_pmutex->lock();
++(*_pcnt);
_pmutex->unlock();
}
shared_ptr& operator=(const shared_ptr& sp)
{
if (_ptr != sp._ptr)
{
release();
_ptr = sp._ptr;
_pcnt = sp._pcnt;
_pmutex = sp._pmutex;
_func = sp._func;
_pmutex->lock();
++(*_pcnt);
_pmutex->unlock();
}
return *this;
}
int use_count()
{
_pmutex->lock();
int ret = (*_pcnt);
_pmutex->unlock();
return ret;
}
T* get() const
{
return _ptr;
}
//像指针一样
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T& operator[](size_t pos)
{
return _ptr[pos];
}
private:
T* _ptr;
int* _pcnt;
std::mutex* _pmutex;
std::function _func;
};
int main()
{
szg::shared_ptr sp1(new string[10], [](string* s) {delete[] s;});
szg::shared_ptr sp2(sp1);
szg::shared_ptr sp3(sp2);
(*sp1) = "111111";
(*sp2) = "222222";
cout << *sp1 << endl;
cout << *sp2 << endl;
sp1 = sp2;
szg::shared_ptr sp4(new string[10], [](string* s) {delete[] s;});
szg::shared_ptr sp5(sp4);
sp1 = sp4;
return 0;
}