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. mysql关键字讲解(join 、order by、group by、having、distinct)

    1.join     1.1 OUTER JOIN:想要包含右侧表中的所有行,以及左侧表中有匹配记录的行.        1.11 Mysql中有左连接(left join):            ...

  2. Metadata file not found - Data.Entity.Model

    错误 3 正在编译转换: 未能找到元数据文件“F:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\Tools\..\IDE\Micr ...

  3. 在有大量数据时 少用In(数据会丢失) 用left join 代替

    select @From, @To, EffectiveDate, GETDATE(), Rate from Config_Currency_ExchangeRate_Temp where Effec ...

  4. DataBase 总结开篇

    系列说明 本系列将总结(SQL)数据库技术在日常开发中引用,读者群体假设为三类:没接触过SQL的入门程序员.有过一两年经验的程序员.三年以上接触过性能调优的程序员.按照这个分类本系列大体分为三篇 第一 ...

  5. 【转载】C#后台声明式验证,远离if验证

    ViewModel public class ViewModel { [Required(ErrorMessage="标题不能为空")] public string Title { ...

  6. WIN7中oracle10g的安装注意事项

    1.本次安装数据库版本为10.2.0.1,操作系统版本为windows7 32位 2.注意在"setup.exe"中以右键属性后,设置以兼容模式及以管理员身份运行该程序:在%安装文 ...

  7. 编译内核,配置内核make menuconfig

    http://blog.csdn.net/xuyuefei1988/article/details/8635539 make make modules_install make install 模块安 ...

  8. CSS 之 @media

    @media 版本:CSS2 兼容性:IE5+ 语法: @media  sMedia  {sRules} 取值: sMedia : 指定设备名称.请参阅设备类型 all, aural, braille ...

  9. python中 “与,或,异或”

    在python编程语言里面: 按位的运算,都按位的运算,都是把参加运算的数的二进制形式进行运算. 1.与运算:A与B值均为1时,A.B与的运算结果才为1,否则为0 (运算符:&) 2.或运算: ...

  10. 如何根据w3wp.exe的进程pid查看是哪个应用程序池

    如何根据w3wp.exe的进程pid查看是哪个应用程序池? 根据iisapp 查看PID所对应的IIS应用程序池及详细介绍: 从IIS6.0可以在IIS中架设多个站点并给每个站点指定不同的应用程序池, ...