最近在看陈硕的《Linux多线程服务端编程:使用muduo C++网络库》,第一章中重点讲了多线程下指针的处理问题,其中重点是 shared_ptr 与 weak_ptr, 上两篇学习了 shared_ptr 的基本用法 ( shared_ptr类模版 ) 和 weak_ptr 的基本用法 ( waek_ptr类模版 ) ,今天继续学习 shared_ptr ,以下是对 Smart Pointer Programming Techniques 的部分中文翻译。
Using incomplete classes for implementation hiding
分离接口与实现的一个有效方式是使用不完整类型类的指针。
1 | class FILE; |
可以使用 shared_ptr 来表达上面的接口,使得不需要手动调用 fclose。
1 | class FILE; |
这种方式是利用了 shared_ptr 能够自动调用 deleter 的机制来去除了 fclose 的显示调用,实际上,当 X 是不完整类型时,shared_ptr< X > 能够被拷贝和销毁。
The “Pimpl” idiom
不完整类的一个 C++ 变种是 “Pimpl” idiom,不完整类没有暴露于用户,shared_ptr 能够用来实现一个 “Pimpl”:
1 | // file.hpp: |
1 | // file.cpp: |
这里的关键点是编译器产生的复制拷贝函数,赋值函数,析构函数都有其意义(代码中file没有定义),因此,file 是可以拷贝复制和赋值的,使得它能够用在标准容器里。
Using abstract classes for implementation hiding
另外一个在 C++ 中分离接口与实现的方式是使用 虚基类 和 工厂函数,虚类有时候被称为接口,这种模式被称为基于接口编程 (“interface-based programming” ),这时,shared_ptr 可以作为工厂函数的返回类型。
1 | // X.hpp: |
留意 X_impl 放在了 cpp 中声明和定义。
1 | //X.cpp: |
shared_ptr 的一个关键属性是分配内存,构造函数,释放内存,析构函数的细节是在工厂函数的构造函数里面开始的。留意例子中的被保护的非虚析构函数(虚析构函数可以确保程序正确调用指针对象的析构函数,如果没有 virtual 关键字,则由指针的类型决定析构函数),客户代码不能够(也不需要)删除指向 X 的指针;createX 返回的 shared_ptr< X > 能够正确地调用 ~X_impl。
Preventing delete px.get()
阻止客户代码试图删除一个被 shared_ptr 管理的指针是经常需要的,前面的技术中展示了其中一个办法,使用 protected 的析构函数,另外一个方法是使用私有的 deleter:
1 | class X |
Using a shared_ptr to hold a pointer to an array
shared_ptr 能够用来保存 new[] 操作出来的数组的指针:
1 | shared_ptr<X> px(new X[1], checked_array_deleter<X>()); |
注意,如果有得选择,shared_array 更经常适合这个任务,它有一个专门为数组设计的接口,不需要 operator* 与 operator->,并且不需要指针的转化。(?)
Encapsulating allocation details, wrapping factory functions
shared_ptr 能够用来创建 C++ 的 wrappers 来适应现有 C 风格的库接口(库接口中工厂函数返回的指针),从而封装内存分配的细节。考虑下面这个接口,CreateX 可能从它的私有堆里分配内存给 X, ~X 可能无法访问,或者 X 可能不是完整的:
1 | X * CreateX(); |
客户端代码调用 createX 不需要知道对象是如何被分配内存的,析构也是自动的。
Using a shared_ptr to hold a pointer to a statically allocated object
有时给一个已经存在的对象创建一个 shared_ptr ,因此,当没有更多引用剩下时,shared_ptr 不试图去销毁这个对象,如下工厂函数:
1 | shared_ptr<X> createX(); |
有些时候需要返回静态分配 X 的指针,解决方法是使用自定义的 deleter 不做任何东西:
1 | struct null_deleter |