目录
[C++ boost 库](#C++ boost 库)
[shared_ptr 的原理](#shared_ptr 的原理)
为什么要使用智能指针
cpp
int main()
{
pair<string, string>* p1 = new pair<string, string>;
delete p1;
cout << "delete:" << p1 << endl;
return 0;
}
上面代码完全没有内存泄漏的问题,但如果在 delete p1 之前,调用了其他函数,而这个函数抛出了异常导致程序的执行流没有执行到 delete p1 语句,就造成了内存泄漏(如果这是一个长期运行的程序)。必须在捕获异常的同时,执行 delete p1 语句:
cpp
void f()
{
pair<string, string>* p1 = new pair<string, string>;
try
{
func();
}
catch (...)
{
delete p1;
cout << "delete:" << p1 << endl;
throw;
}
delete p1;
cout << "delete:" << p1 << endl;
}
int main()
{
try
{
f();
}
catch (const exception& e)
{
cout << e.what() << endl;
}
return 0;
}
如果我多定义几个 p 指针:
cpp
void f()
{
pair<string, string>* p1 = new pair<string, string>;
pair<string, string>* p2 = new pair<string, string>;
pair<string, string>* p3 = new pair<string, string>;
pair<string, string>* p4 = new pair<string, string>;
//...
new 可能抛异常(即使概率很低),如果 p1 抛异常,那什么也不用释放,如果 p2 抛异常,要释放 p1,如果 p3 抛异常,要释放 p1 和 p2......,不仅是 new 可能抛异常,func 函数也可能抛异常,如果这些异常都要处理,这使得代码变得非常冗杂。如何解决这个问题呢?可以使用智能指针
智能指针的原理
**RAII(Resource Acquisition Is Initialization)**是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源 ,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:
- 不需要显式地释放资源。
- 采用这种方式,对象所需的资源在其生命期内始终保持有效
智能指针其实就是把一个指针交给一个类对象进行管理,当退出某个作用域,不管是正常退出还是抛异常退出,都会调用对象的析构函数,释放空间。
cpp
int div()
{
int a, b;
cin >> a >> b;
if (b == 0)
throw invalid_argument("除0错误");
return a / b;
}
template<class T>
class SmartPtr
{
public:
SmartPtr(T* ptr)
:_ptr(ptr)
{}
~SmartPtr()
{
cout << "delete:" << _ptr << endl;
delete _ptr;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
void f()
{
SmartPtr<pair<string, string>> sp1(new pair<string, string>("1111", "22222"));
//div();
SmartPtr<pair<string, string>> sp2(new pair<string, string>);
SmartPtr<pair<string, string>> sp3(new pair<string, string>);
SmartPtr<string> sp4(new string("xxxxx"));
cout << *sp4 << endl;
cout << sp1->first << endl;
cout << sp1->second<< endl;
div();
}
int main()
{
try
{
f();
}
catch (const exception& e)
{
cout << e.what() << endl;
}
return 0;
}
有了智能指针,对象在退出某个作用域时(不管是正常退出还是抛异常退出),自动调用析构函数释放资源,再也不用手动 delete,也不怕忘记 delete 了。
总结一下智能指针的原理:
1. RAII特性
2. 重载 operator* 和 opertaor->,具有像指针一样的行为。
std::auto_ptr
上面的指针还远远不能解决一些问题,比如:
cpp
SmartPtr<string> sp1(new string("xxxx"));
SmartPtr<string> sp2(new string("yyyy"));
sp1 = sp2;
sp1 和 sp2 都指向 "xxxx",会导致重复析构的问题。为了解决这个问题,auto_ptr 采用管理权转移的思想 。即:把资源交给拷贝对象管理,自己变成空指针。比如上面的 sp1 = sp2; 就是把 "yyyy" 的管理权交给 sp1 管理,sp1 不再管理 "xxxx",sp2 置为空。由于****auto_ptr 会导致被拷贝对象悬空问题,所以一般不会使用 auto_ptr。面试时,不要手撕 auto_ptr
auto_ptr 的拷贝构造和赋值重载:
cpp
auto_ptr(auto_ptr<T>& sp)
:_ptr(sp._ptr)
{
// 管理权转移
sp._ptr = nullptr;
}
auto_ptr<T>& operator=(auto_ptr<T>& ap)
{
// 检测是否为自己给自己赋值
if (this != &ap)
{
// 释放当前对象中资源
if (_ptr)
delete _ptr;
// 转移ap中资源到当前对象中
_ptr = ap._ptr;
ap._ptr = NULL;
}
return *this;
}
C++ boost 库
C++ boost 库简介
Boost是由C++标准委员会库工作组成员发起的跨平台开源C++程序库集合,采用Boost License授权协议,为C++标准库提供扩展功能。其部分组件已被纳入C++技术规范TR1和TR2。Boost社区通过提供标准化参考实现推动C++语言演进,开发成果多次被C++标准提案采纳。需注意的是,Boost包含部分实验性质的组件,实际开发中需谨慎评估适用性。
C++11出来之前,boost库已经实现了更好用的 scoped_ptr / shared_ptr / weak_ptr
C++11将boost库中智能指针精华部分吸收了过来

std::unique_ptr
C++11中开始提供更靠谱的unique_ptr,它的实现原理:简单粗暴的不让拷贝。
cpp
unique_ptr(const unique_ptr<T>& sp) = delete;
unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;
不需要拷贝的场景,可以使用 unique_ptr ,但有一些需要拷贝的地方,可以使用 shared_ptr
std::shared_ptr
shared_ptr 的原理
C++11中开始提供更靠谱的并且支持拷贝的 shared_ptr
shared_ptr的原理 :是通过引用计数 的方式来实现多个 shared_ptr 对象之间共享资源。
老师晚自习在下课之前都会通知,让最后走的学生记得把灯关了。
shared_ptr在其内部,给每个资源都维护一份计数,用来记录该份资源被几个对象共享。
在对象被销毁时(也就是析构函数调用),说明自己不使用该资源,对象的引用计数减1。
如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源。
如何实现引用计数,记录该份资源被几个对象共同管理?
在类的数据成员再增加一个静态变量可不可以呢?不可以,比如下面的场景:
解决方法是在类中增加一个指向引用计数的指针,让这个指针指向它管理的资源的引用计数:
模拟实现:
cpp
template<class T>
class shared_ptr
{
public:
shared_ptr(T* ptr = nullptr)
:_ptr(ptr)
, _pcount(new int(1))
{}
~shared_ptr()
{
if (--(*_pcount) == 0)
{
cout << "delete:" << _ptr << endl;
delete _ptr;
delete _pcount;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
shared_ptr(const shared_ptr<T>& sp)
:_ptr(sp._ptr)
, _pcount(sp._pcount)
{
++(*_pcount);
}
shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{
if (_ptr == sp._ptr)
return *this;
if (--(*_pcount) == 0)
{
delete _ptr;
delete _pcount;
}
_ptr = sp._ptr;
_pcount = sp._pcount;
++(*_pcount);
return *this;
}
int use_count() const
{
return *_pcount;
}
T* get() const
{
return _ptr;
}
private:
T* _ptr;
int* _pcount;
};
shared_ptr的循环引用
shared_ptr 在绝大多数情况下是安全的,但在某些场景可能还是会造成内存泄漏:
cpp
struct ListNode
{
int _data;
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);
node1->_next = node2;
node2->_prev = node1;
return 0;
}

在退出 mian 函数调用析构函数时,node1 和 node2 的引用计数为1,虽然调用了析构函数,但它们没有被释放,而它们现在确实应该要被释放。并且出现了逻辑死循环,这种现象就叫"循环引用"

解决方案:在引用计数的场景下,把节点中的 _prev 和 _next 改成 weak_ptr 就可以了
原理就是,node1->_next = node2;和node2->_prev = node1;时 weak_ptr 的_next 和 _prev不会增加 node1 和 node2 的引用计数。
cpp
struct ListNode
{
int _data;
weak_ptr<ListNode> _prev;
weak_ptr<ListNode> _next;
~ListNode(){ cout << "~ListNode()" << endl; }
};
weak_ptr 不是 RAII 的智能指针,是专门用来解决循环引用的指针
模拟实现:
cpp
template<class T>
class weak_ptr
{
public:
weak_ptr()
:_ptr(nullptr)
{}
weak_ptr(const shared_ptr<T>& sp)
:_ptr(sp.get())
{}
weak_ptr<T>& operator=(const shared_ptr<T>& sp)
{
_ptr = sp.get();
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
智能指针总结
- auto_ptr 管理权转移,会导致被拷贝对象悬空,建议不要使用它
- unique_ptr 禁止拷贝,简单粗暴。日常使用,不需要拷贝的场景,建议使用它
- shared_ptr 引用计数支持拷贝,需要拷贝的场景,就使用它。但是要小心构成循环引用,循环引用会导致内存泄漏
- weak_ptr专门解决 shared_ptr 的循环引用问题
定制删除器
上面的 shared_ptr 还有一个问题没有解决:如果用 new string[10] 来初始化 shared_ptr,shared_ptr 的析构函数是 delete 而不是 delete[ ] ,会导致未定义行为和内存泄漏。如果用 (int*)malloc(sizeof(int)) 初始化 shared_ptr,malloc 必须与 free 匹配。又比如 fopen 要和 fclose 匹配。
定制删除器的使用方法:使用仿函数或 lambda 表达式控制资源如何释放
cpp
template<class T>
struct DeleteArray
{
void operator()(T* ptr)
{
delete[] ptr;
}
};
//定制删除器
int main()
{
shared_ptr<A> sp1(new A[10], DeleteArray<A>());
shared_ptr<A> sp2((A*)malloc(sizeof(A)), [](A* ptr) {free(ptr); });
shared_ptr<FILE> sp3(fopen("Test.cpp", "r"), [](FILE* ptr) {fclose(ptr);});
return 0;
}
定制删除器的模拟实现
观察库里的 shared_ptr 的模板参数和它的构造函数的模板参数:
template <class T> class shared_ptr;
template <class U, class D> shared_ptr (U* p, D del);
D del 就是定制删除器,但它只作为构造函数的参数,而不是定义 shared_ptr 时的模板参数,那么问题来了:怎么在析构函数拿到定制删除器呢?解决方法就是在 shared_ptr 类中定义一个包装器:function<void(T*)> _del,在构造函数初始化它。因为不管是那种析构方式,析构函数的返回值一定是 void,参数一定是一个指针。对于简单的 new 与 delete 匹配,在构造 shared_ptr 时如果不想给定制删除器参数,可以给包装器一个缺省值。
cpp
template<class D>
shared_ptr(T* ptr, D del)
:_ptr(ptr)
, _pcount(new int(1))
, _del(del)
{}
~shared_ptr()
{
if (--(*_pcount) == 0)
{
cout << "delete:" << _ptr << endl;
//delete _ptr;
_del(_ptr);
delete _pcount;
}
}
function<void(T*)> _del = [](T* ptr) {delete ptr; };

