话不多说上代码!
void div()
{
int* p = new int;
int m = 0;
int n = 0;
cin >> m >> n;
if (n == 0)
{
throw exception();
}
cout << "continue" << endl;
cout << m / n;
delete p;
}
int main()
{
try
{
div();
}
catch (exception& e)
{
cout << "除零错误!" << endl;
}
return 0;
}
如果这里发生除零错误,那么这里很明显new出来的指针p并没有被释放,这将导致内存泄漏
什么是内存泄漏呢?
含义:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而 造成了内存的浪费。
内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会 导致响应越来越慢,最终卡死。
如何避免呢?这里我们就要提到我们智能指针了。
智能指针原理和思想
RAII
RAII(Resource Acquisition Is Initialization
)是一种利用对象生命周期来控制程序资源(如内存、文件句 柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源
,接着控制对资源的访问使之在对象的生命周期内始终保持有效,
最后在对象析构的
时候释放资源
。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:
- 不需要显式地释放资源。
- 采用这种方式,对象所需的资源在其生命期内始终保持有效。
template<class T>
class SmartPtr {
public:
SmartPtr(T* ptr)
:_ptr(ptr)
{}
~SmartPtr()
{
if (_ptr)
{
cout << "delete:" << _ptr << endl;
delete _ptr;
}
}
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
private:
T* _ptr;
};
无论抛异常还是正常程序结束,对象都会释放,资源也就释放了。
智能指针的发展
智能指针的分类
auto_ptr
当自定义类型拷贝同类型,会发生浅拷贝,释放同一块内存,它采用管理权转移来解决这个问题
但是原来的指针已经被释放,悬空指针。
unique_ptr
C++11中吸收的第一个指针,解决拷贝的问题,直接给他delete掉赋值和拷贝构造,所以不需要拷贝的场景可以使用unique_ptr。
shared_ptr
shared_ptr
的原理:是通过引用计数的方式来实现多个
shared_ptr
对象之间共享资源。
1. shared_ptr
在其内部,
给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享
。
2.
在
对象被销毁时
(
也就是析构函数调用
)
,就说明自己不使用该资源了,对象的引用计数减一。
3.
如果引用计数是
0
,就说明自己是最后一个使用该资源的对象,
必须释放该资源
;
4. 如果不是
0
,就说明除了自己还有其他对象在使用该份资源,
不能释放该资源,否则其他对象就成野指针了。
shared_ptr只是保证计数的线程安全,但不保证资源的线程安全。
实现简易shared_ptr
template<class T>
class shared_ptr {
public:
shared_ptr(T* ptr)
:_ptr(ptr)
, _count(new int(1))
,_mutex(new mutex)
{}
~shared_ptr()
{
ReleaseRef();
}
shared_ptr(const shared_ptr<T>& sp)
:_ptr(sp._ptr)
, _count(sp._count)
,_mutex(sp._mutex)
{
AddRef();
}
shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{
if (_ptr != sp._ptr)
{
ReleaseRef();
_count = sp._count;
_ptr = sp._ptr;
_mutex = sp._mutex;
AddRef();
}
return *this;
}
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
int use_count()
{
return *_count;
}
private:
void ReleaseRef()
{
_mutex->lock();
bool flag = false;
if (--(*_count) == 0)
{
cout << "delete:" << _ptr << endl;
delete _ptr;
delete _count;
flag = true;
}
_mutex->unlock();
if (flag == true)
{
delete _mutex;
}
}
void AddRef()
{
_mutex->lock();
++(*_count);
_mutex->unlock();
}
private:
T* _ptr;
int* _count;
mutex* _mutex;
};
循环引用问题
看这样一段代码
class ListNode{
public:
shared_ptr<ListNode> _prev;
shared_ptr<ListNode> _next;
~ListNode() { cout << "~ListNode()" << endl; }
};
int main()
{
shared_ptr<ListNode> node1(new ListNode);
shared_ptr<ListNode> node2(new ListNode);
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
node1->_next = node2;
node2->_prev = node1;
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
return 0;
}
这里会产生死锁,node1->next=node2,node2->prev=node1;引用计数成为2;
在这里node1析构但是next还是指向下一个node2;也就是说next析构,node2就释放了;prev析构,node1就释放了。
node1释放之后next才能析构,next析构node2释放,node2释放prev析构,prev析构node1才能释放,产生死锁。
怎样解决呢?
std库里的weak_ptr(弱指针)给出了解决方案,当node1->next=node2,node2->prev=node1;引用计数不会增加;不参与资源的管理(应用场景)
shared_ptr的定制删除器
如果是new[]则需要定制删除器来释放资源->删除器控制释放资源的方式。
template<class T>
struct deleteArr {
void operator()(const T* ptr)
{
cout << "deleteArr->delete []" << endl;
delete[] ptr;
}
};
shared_ptr<ListNode> node1(new ListNode[10], deleteArr<ListNode>());