There’s a lot to love about standard smart pointers in general, and unique_ptr in particular.

Problem

JG Question

1.什么时候你应该使用shared_ptr vs unique_ptr?尽可能列出你所想到的注意事项。

Guru Question

2.为什么你应该总是使用make_shared来创建一个被shared_ptr(s)拥有的对象?请解释。

3.为什么你应该总是使用make_unique来创建一个被unique_ptr拥有的对象?请解释。

4.怎么处理auto_ptr?

Solution

1.什么时候你应该使用shared_ptr vs unique_ptr?尽可能列出你所想到的注意事项。

当存在疑问时,默认情况下优先使用unique_ptr。如果有需要的话可以以后再改为shared_ptr。如果你从一开始就知道你所需要的共享所有权类型指针,那么可以直接通过make_shared来声明shared_ptr类型(查看下面的#2)。

有三个理由解释为什么“当存在疑问时,默认情况下优先使用unique_ptr”。

首先,使用最简单的语义应该是足够的:选择正确的智能指针来最直接表达你的意图和你(现在)所需要的。如果你创建了一个新类型,但是不知道最终是否需要共享对象所有权,使用unique_ptr来表达唯一的所有权。或者你可以将它放入容器中(例如:vector<unique_ptr<widget>>),然后做你想要用原始指针(raw pointer)做的事情,不会有问题,除了很安全。如果之后需要共享所有权,你可以直接将unique_ptr改变为shared_ptr。

其次,unique_ptr比shared_ptr更高效。unique_ptr不需要去维护引用计数信息和底层的控制块。它设计的目的就是尽可能简单便宜地像原始指针一样移动和使用。当你没有要求比你需要的多,那么你就没有必要去承受不使用那部分带来的开销。

最后,使用unique_ptr更加灵活且有保持选择的权利。如果一开始就使用unique_ptr,你之后可以总是转换到shared_ptr或者通过.get()或.release()来使用其他自定义的智能指针(甚至是原始指针)。

Guideline:优先使用标准的智能指针。默认情况下使用unique_ptr,如果需要共享则使用shared_ptr。它们在C++标准库中属于常见类型。只有在和其他库进行交互的必要前提下才使用其他智能指针类型,或使用标准的智能指针的deleters和allocators达不到你的需求的自定义行为。

2.为什么你应该总是使用make_shared来创建一个被shared_ptr(s)拥有的对象?请解释。
     注意:如果你需要是同自定的allocator来创建一个对象,你可以使用allocate_shared,这几乎很少见。尽管它的名字稍微有点不一样,但allocate_shared应该被视为“可以让你指定一个allocator风味的make_shared”。所以在这主要讨论make_shared而并不会去区分它们之间的区别。

有两种主要情况你不能使用make_shared去创建一个被shared_ptr(s)拥有的对象:(a)你需要使用自定义的deleter,这种情况下因为使用shared_ptr来管理非内存资源或在非标准内存区分配一个对象,你不能使用make_shared因为它不支持指定的deleter;(b)如果你采取一个原始指针(raw pointer)指向一个来需要处理的自其他(通常是legacy code)代码的对象,你可以直接用原始指针去构建shared_ptr。

              Guideline:使用make_shared(或需要自定义allocator的allocate_shared)去创建你知道需要使用shared_ptr类型的对象,除非你需要一个自定义的deleter或从采用一个来自其他地方的原始指针(raw pointer)。

那么,为什么几乎总是尽可能地使用make_shared(或需要自定义allocator的allocate_shared)?这里有两个主要的原因:简单和高效。
第一,使用make_shared的代码更简单,书写清晰和正确的代码是第一位。
第二,使用make_shared更高效。shared_ptr需要在控制块维护一个被所有share_ptr(s)和weak_ptr(s)引用的指定对象的内务信息(housekeeping information)。这个内务信息包含了不止一个引用计数,而是两个。

· 一个“强引用”来跟踪当前保持活动的shared_ptr(s)的个数。当最后一个强引用消失时这个共享对象就会被销毁(可能会被回收)。
    · 一个“弱引用”来跟踪当前观察对象的weak_ptr(s)的个数。当最后一个弱引用消失时,这个共享的内务控制块会被销毁和回收(如果还没有被回收话)。

如果你分配对象分别通过原始的new表达式,然后再传给shared_ptr,那么shared_ptr会没有选择只能分别去分配控制块,如下面代码和图所示。

// Example 2(a): Separate allocation
auto sp1 = shared_ptr<widget>{ new widget{} };
auto sp2 = sp1;

我们想要避免两次分别的分配动作。如果选择使用make_ptr去分配对象,那么shared_ptr所有的只会有一个。然后它的实现可能将他们折叠在一起到一个分配当中。如下面代码和图所示。

// Example 2(b): Single allocation
auto sp1 = make_shared<widget>();
auto sp2 = sp1;

组合分配有两个主要好处:
     · 减少了分配开销,包括内存碎片。首先,这个最明显的方式是通过减少分配请求的次数,分配一般是属于昂贵的操作。这同时也减少了在allocators上的竞争;其次,只使用一块内存而不是两块可以减少每次分配的开销。当向系统要求一块内存时,系统一般至少会给出一些字节,通常还会给出更多一些,因为里面维护其他的信息需要一定的空间。因此通过使用单个内存块,
倾向于减少额外的开销,最终减少因为零碎的内存区而导致的内存碎片。
     · 改善了区域(locality)。引用计数经常会被对象使用,对于小的对象很可能是处在相同的内存线(cache line)上,这个可以提高缓存性能(只要当中没有一些线程将智能指针拷贝到一个紧密的循环中,不要那么做)。

像往常一样,如果能试图通过一个简单的函数调用来表达更多的意图,那么系统会找到一个更高效的方式来完成任务。这个可以通过往vector中填充元素可以看出,因为使用v.inser(first,last)填充100个元素远比调用100次v.inser(value)更高效。所以使用单个make_shared调用比分别调用new widget()和shared_ptr(widget*)更好。

此外还有两个好处是:使用make_shared避免了显示使用new和避免了一些异常安全问题。上述这些同样适用于make_unique。

3.为什么你应该总是使用make_unique来创建一个被unique_ptr拥有的对象?请解释。

和make_shared一样。存在两个主要情况不能使用make_unique分配一个unique_ptr所有权类型的对象:如果需要一个deleter,或采用一个原始指针(raw pointer)。

否则,尽可能地优先使用make_unique。

Guideline: 使用make_unique创建一个非共享的对象,除非需要一个自定义的deleter或采用一个来自于其他地方的原始指针。

因为make_shared和make_unique有对称性,所以和上述一样。首先,优先使用make_unique<T>()而不是繁琐的unique_ptr<T>{ new T{}},因为通常情况下应该避免显示使用new

           Guideline:不要显示使用new,delete,和*指针。除非需要实现一些底层的数据结构这种罕见的情况。

其次,它避免了使用原始new的一些异常安全问题。下面是个例子:

void sink( unique_ptr<widget>, unique_ptr<gadget> );

sink( unique_ptr<widget>{new widget{}},
unique_ptr<gadget>{new gadget{}} ); // Q1: do you see the problem?

简单说就是,如果首先分配和构建new widget,然后再分配或构建new gadget时发生了异常,那么widget则会泄露。有人可能会像:“这样的话,那么可以调整new widget{}到make_unique<widget>()这样就没问题了对吧?”也就是:

sink( make_unique<widget>(),
unique_ptr<gadget>{new gadget{}} ); // Q2: is this better?

答案是否定的。因为C++在评估函数参数的序列是未指定的,因此new widget或new gadget都可能先执行。

因此,只是改变参数中的一个都没有堵上这个漏洞,只有两者都使用make_unique才真正完全消除了这个问题:

sink( make_unique<widget>(), make_unique<gadget>() );  // exception-safe

关于异常安全的问题会在GotW #56更多的讨论。

Guideline:分配对象时,默认情况下优先选择使用make_unique。当知道一个对象的生命期需要通过shared_ptr(s)进行管理,那么使用make_shared。

4.怎么处理auto_ptr?

auto_ptr在C++有移动语义前有很多特性是作为去创建一个和unique_ptr的意思。auto_ptr现在是废弃了的。不应该在新写的代码中使用它。

如果在既存代码中使用了auto_ptr,那么可以进行全局搜索然后用unique_ptr去替换它。绝大多数的使用都会工作的一样,可能会暴露一些问题(编译错误)或修复了(轻微)一些bug或者两个你都不知道有。

[译]GotW #89 Smart Pointers的更多相关文章

  1. [c++] Smart Pointers

    内存管理方面的知识 基础实例: #include <iostream> #include <stack> #include <memory> using names ...

  2. [译]GotW #6b Const-Correctness, Part 2

         const和mutable对于书写安全代码来说是个很有利的工具,坚持使用它们. Problem Guru Question 在下面代码中,在只要合适的情况下,对const进行增加和删除(包括 ...

  3. [译]GotW #6a: Const-Correctness, Part 1

    const 和 mutable在C++存在已经很多年了,对于如今的这两个关键字你了解多少? Problem JG Question 1. 什么是“共享变量”? Guru Question 2. con ...

  4. [译]GotW #4 Class Mechanics

    你对写一个类的细节有多在行?这条款不仅注重公然的错误,更多的是一种专业的风格.了解这些原则将会帮助你设计易于使用和易于管理的类. JG Question 1. 什么使得接口“容易正确使用,错误使用却很 ...

  5. [译]GotW #3: Using the Standard Library (or, Temporaries Revisited)

    高效的代码重用是良好的软件工程中重要的一部分.为了演示如何更好地通过使用标准库算法而不是手工编写,我们再次考虑先前的问题.演示通过简单利用标准库中已有的算法来避免的一些问题. Problem JG Q ...

  6. [译]GotW #2: Temporary Objects

        不必要的和(或)临时的变量经常是罪魁祸首,它让你在程序性能方面的努力功亏一篑.如何才能识别出它们然后避免它们呢? Problem JG Question: 1. 什么是临时变量? Guru Q ...

  7. [译]GotW #1: Variable Initialization

    原文地址:http://herbsutter.com/2013/05/09/gotw-1-solution/ 第一个问题强调的是要明白自己在写什么的重要性.下面有几行简单的代码--它们大多数之间都有区 ...

  8. [译]GotW #5:Overriding Virtual Functions

       虚函数是一个很基本的特性,但是它们偶尔会隐藏在很微妙的地方,然后等着你.如果你能回答下面的问题,那么你已经完全了解了它,你不太能浪费太多时间去调试类似下面的问题. Problem JG Ques ...

  9. [译]GotW #1: Variable Initialization 续

    Answer 2. 下面每行代码都做了什么? 在Q2中,我们创建了一个vector<int>且传了参数10和20到构造函数中,第一种情况下(10,20),第二种情况是{10, 20}. 它 ...

随机推荐

  1. C#中的集合

    [集合不同于数组,是一组可变类型的.可变数量的元素的组合,这些元素可能共享某些特征,需要以某种操作方式一起进行操作.一般来讲,为了便于操作这些元素的类型是相同的] [集合与数组的区别:数组是连续的.同 ...

  2. wamp优化

    友情链接:IT狂人博客 转载请注明作者:浮沉雄鹰 和本文链接:http://www.cnblogs.com/xby1993/p/3342085.html 一.修改php.ini, 修改上传文件大小限制 ...

  3. iOS 安全:UIWebView访问Https站点防止中间人攻击

    尽管Https协议能够提供数据的加密.身份的认证等安全服务,但并不是没有漏洞.HTTPS协议安全隐患的存在可能使用户受到各种极具破坏力的网络攻击.其中中间人攻击(Man In The Middle, ...

  4. 线程间通信--生产者消费者 升级版JDK5

    import java.util.concurrent.locks.*; /*1.新的解锁,上锁操作,据说是jdk5.0升级版,以前的枷锁,解锁都是隐藏的,默认的,现在变成显式 2.新的异常处理方式  ...

  5. 省市县 三级 四级联动Javascript JQ 插件PCASClass.js

    想要使用这款组件,需要页面引入 PCASClass.js 核心文件,该文件在您的HTML文档<head>标签之内. <script type="text/javascrip ...

  6. Redis与Memcached的比较

    网络IO模型 Memcached 是多线程,非阻塞IO复用的网络模型,分为监听主线程和worker子线程,监听线程监听网络连接,接受请求后,将连接描述字pipe 传递给worker线程,进行读写IO, ...

  7. HTML5-原声拖放

    最早在网页中引入js拖放功能的是IE4,并且只可以拖放图像和某些文本.IE5.5以后网页中的任何元素都可以进行拖放.HTML5以IE为实例制定了拖放规范.FireFox3.5.Safari3+和Chr ...

  8. 自定义弹出div对话框

    <style type="text/css"> html,body{height:100%;overflow:hidden;} body,div,h2{margin:0 ...

  9. PHP 递归创建目录

    /* 用迭代的方法递归创建目录 其实在PHP5.0.0之后mkdir就已经能递归创建目录了. 这里主要是自己学习迭代,所以拿创建级联目录开刀了. 开发中应该写mkdir('./a/b/c/d/e',0 ...

  10. 推荐一款java的验证码组件——kaptcha

    使用方法: 项目中导入kaptcha-2.3.jar包 在web.xml里面新增:   <!-- 登陆验证码Kaptcha --> <servlet> <servlet- ...