前言
- 面对异常处理时,随时都有可能跳出当前栈帧取寻找匹配的 catch 语句;导致栈帧被销毁时,当前栈帧的资源(堆内存泄露等)未被处理。
- 智能指针应用而生,也伴随着一些智能指针的发展史。想要达到的效果是:让智能指针管理这块儿资源,出作用域自动销毁。
- 也就是RAII获取资源即初始化,一个类型的局部对象,用刚申请的指针去构造;然后就是解决拷贝构造和赋值重载的问题。
智能指针的原理
auto_ptr
- 一旦发生拷贝,就将ap中资源转移到当前对象中,然后另ap与其所管理资源断开联系,这样就解决了一块空间被多个对象使用而造成程序奔溃问题
- 这样会发现导致ap对象悬空,通过ap对象访问资源时就会出现问题。已被废弃。
AutoPtr(AutoPtr<T>& ap)
: _ptr(ap._ptr)
{
ap._ptr = NULL;
}
unique_ptr
-
unique_ptr
官方文档 - 简单直接,不需要用拷贝构造的时候,就可以用它。
UniquePtr(UniquePtr<T> const &) = delete;
UniquePtr & operator=(UniquePtr<T> const &) = delete;
shared_ptr
- shared_ptr智能指针(超级详细)
- 通过增加一个堆上的计数器,保证有多个对象可以使用同一个空间,且能安全释放析构。
- 多线程环境下,需要对引用计数进行锁保护。
- 存在循环引用的场景,需要搭配weak_ptr解决。
#include <thread>
#include <mutex>
template <class T>
class SharedPtr
{
public:
SharedPtr(T* ptr = nullptr)
: _ptr(ptr)
, _pRefCount(new int(1))
, _pMutex(new mutex)
{}
....
private:
int* _pRefCount; // 引用计数
T* _ptr; // 指向管理资源的指针
mutex* _pMutex; // 互斥锁
};
- 获取指针的地址
- 获取有多少个对象在使用它
int UseCount()
{
return *_pRefCount;
}
T* Get()
{
return _ptr;
}
- 像普通指针的解引用
- 取数据的地址
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
- 线程安全,计数的保护
- 拷贝构造
SharedPtr(const SharedPtr<T>& sp)
: _ptr(sp._ptr)
, _pRefCount(sp._pRefCount)
, _pMutex(sp._pMutex)
{
AddRefCount();
}
void AddRefCount()
{
// 加锁或者使用加1的原子操作
_pMutex->lock();
++(*_pRefCount);
_pMutex->unlock();
}
- 赋值重载
// sp1 = sp2
SharedPtr<T>& operator=(const SharedPtr<T>& sp)
{
if (_ptr != sp._ptr)
{
// 释放管理的旧资源
Release();
// 共享管理新对象的资源,并增加引用计数
_ptr = sp._ptr;
_pRefCount = sp._pRefCount;
_pMutex = sp._pMutex;
AddRefCount();
}
return *this;
}
- 析构:
~SharedPtr()
{
Release();
}
void Release()
{
bool deleteflag = false;
// 引用计数减1,如果减到0,则释放资源
_pMutex.lock();
if (--(*_pRefCount) == 0)
{
delete _ptr;
delete _pRefCount;
deleteflag = true;
}
_pMutex.unlock();
if(deleteflag == true)
delete _pMutex;
}
循环引用原因及解决
- 存在这样的场景:当两个 shared_ptr 对象 只连接一个时,无事发生,都会被析构
struct list_node
{
shared_ptr<list_node> _prev;
shared_ptr<list_node> _next;
int value = 24;
list_node(int x)
:_prev(nullptr)
,_next(nullptr)
,value(x)
{}
};
void test_circulate_cite_notsafe()//循环引用的内存泄漏。
{
shared_ptr<list_node> spl1 = new list_node(24);
shared_ptr<list_node> spl2 = new list_node(25);
cout << spl1.count() << endl;
cout << spl2.count() << endl;
spl1->_next = spl2; //连接一个不会有问题
spl2->_prev = spl1;
cout << spl1.get() << "---->" << spl1.count() << endl;
cout << spl2.get() << "---->" << spl2.count() << endl;
}
- 当两个互相连接时,就存在内存泄露:
weak_ptr
- 解决方案:用
weak_ptr
- 在产生循环引用的地方使用,不参与管理资源,但可以访问修改资源
- 支持用
struct list_node1
{
weak_ptr<list_node1> _prev;
weak_ptr<list_node1> _next;
int value;
list_node1(int x = 34)
:_prev(nullptr)
, _next(nullptr)
, value(x)
{}
~list_node1()
{
cout << " ~list_node1() " << endl;
}
};
定制删除器
- 为了保证保证new和delete配对使用:
- 仿函数删除器:
// 仿函数的删除器
template<class T>
struct FreeFunc
{
void operator()(T* ptr)
{
cout << "free:" << ptr << endl;
free(ptr);
}
};
template<class T>
struct DeleteArrayFunc
{
void operator()(T* ptr)
{
cout << "delete[]" << ptr << endl;
delete[] ptr;
}
};
- 使用
FreeFunc<int> freeFunc;
shared_ptr<int> sp1((int*)malloc(4), freeFunc);
DeleteArrayFunc<int> deleteArrayFunc;
shared_ptr<int> sp2((int*)malloc(4), deleteArrayFunc);
std::shared_ptr<int> p7(new int[10], [](int* p) {delete[]p; });
内存泄露的危害
- 内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
- 内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。
内存泄露的分类
- 堆内存泄漏(Heap leak)
- 堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。
- 系统资源泄漏
- 指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。
内存泄露的检测与防治
- 有讲
- 内存泄漏非常常见,解决方案分为两种:1、事前预防型。如智能指针等。2、事后查错型。如泄漏检测工具。