C++核心技术:智能指针

10 分钟阅读 1026 字 + 451 词
shared_ptr里有两个指针,其中一个指向控制块,一个指向真正的对象
shared_ptr的复制:复制两个指针,然后在控制块的引用计数+1
假设我们使用容器v来存储智能指针,那对于unique_ptr,每次执行push_back操作时必须使用一个右值,如下面:
v.push_back(createShape()); //createShape通过make_unique返回智能指针
v.push_back(std::move(uptr));//移动之后我们就不应该再使用uptr了
如何选择unique还是shared?
如果可以找到一个对象来明确负责另外一个对象的生存期,那么unique_ptr就足够了;只有在做不到这点时,才考虑使用shared_ptr。
使用shared_ptr的场景:
  1. 消息有多个并发处理者
  2. 异步代码需要把当前对象的进一步处理注册到某个异步回调上
  3. 写时复制的对象,允许同时读和修改的树形结构
weak_ptr的解决环形引用的问题,通过在控制块里放置一个独立的弱引用计数。
通过weak_ptr的lock可以得到shared_ptr,如果返回指针为空,那么代表指向对象已经失效
引用计数的增减是原子操作 ,这在 多线程环境 里很容易导致 性能问题 。如果按值传递shared_ptr,就会发生这类问题。
不过解决这个问题的正确方式是 在无关生存期的函数接口中不要使用智能指针 使用普通的引用(保证对象存在)或指针(对象不存在,指针为空)就好
Facebook在rocksDB里shared_ptr从值传参改成传普通引用/指针,该优化在某项基准测试中使得每秒查询次数提升了原来的4倍。
使用shared_ptr时,我们常常需要创建局部变量来增加引用计数,以防止被引用的对象在使用期间因全局shared_ptr被修改而被销毁。如果使用这种用法的话,要确保这样的代码放在不会反复执行的地方,以免影响性能。
智能指针的传递方式
还是这句话, 在无关生存期的函数接口中不要使用智能指针 使用普通的引用(保证对象存在)或指针(对象不存在,指针为空)就好
反过来,如果一个函数确实就是要管理对象的所有权,那传参/返回值就该用智能指针,如下:
unique_ptr<Obj> factory();//生产Obj
void sink(unique_ptr<Obj>);//消费Obj,转移所有权
void reseat(unique_ptr<Obj>&);//可能修改指针
void thinko(const unique_ptr<Obj>&);//这种情况就不需要使用智能指针了,因为不涉及到管理对象的所有权,这样使用智能指针没什么意义,完全可以用对象引用或者指针代替
shared_ptr<Obj> factory();//确知生产结果需要共享
void share(shared_ptr<Obj>);//将保留引用计数
void reseat(shared_ptr<Obj>&);//可能修改指针
void may_share(const shared_ptr<Obj>&);//可能保留引用计数
出于性能考虑,在最终保存智能指针时,我们都应该用右值形式如:
_saved = std::move(ptr);
删除器的行为定制
unique_ptr的删除器是模板的第二个参数,但是shared_ptr则没有将删除器当作模板参数。
这个区别的原因是: unique_ptr追求的是低开销,和裸指针开销相似。而shared_ptr本来就需要为控制块额外分配内存,再增加删除操作的信息也不是什么问题。
对于shared_ptr来说,尽可能使用make_shared来创造智能指针,因为可以一次性分配控制器和对象的内存。
shared_from_this()细节
当我们继承了enable_shared_from_this的时候,可以利用shared_from_this()来从this指针获得一个shared_ptr。这样的类内部有一个指向自己的weak_ptr,但这个weak_ptr需要外部创建shared_ptr的时候来进行初始化,因此我们在构造函数内部不能立即开始执行利用到shared_from_this()的函数,否则shared_from_this会失败。
c++核心指南里和裸指针相关的条款
  • 1.11:永远不要用原始指针(T*)或引用(T&)来转移所有权
  • F.7:对于一般用途,使用原始指针(T*)或引用(T&)作为参数,而不是智能指针。
  • C.12:不要把可拷贝或可移动类型的数据成员变成const或引用。
  • R.3:原始指针(T*)不代表所有权