GotW #89智能指针的一些建议

Posted xiongping_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了GotW #89智能指针的一些建议相关的知识,希望对你有一定的参考价值。

question:
1.什么时候应该使用shared_ptr vs. unique_ptr? 列出尽可能多的注意事项。
2.为什么你几乎总是使用make_shared来创建一个由shared_ptrs拥有的对象? 说明。
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。你仍然可以把它放在一个容器(例如,向量<unique_ptr <widget >>),并做大多数其他事情,你想做一个原始指针,只有安全。如果以后需要共享所有权,则可以将unique_ptr总是移动转换为shared_ptr。
第二,unique_ptr比shared_ptr更有效。 unique_ptr不需要维护引用计数信息和覆盖下的控制块,并且被设计为移动和使用作为原始指针很便宜。当你不要求超过你需要,你不会招致你不会使用的开销。
第三,从unique_ptr开始更灵活,并保持您的选择打开。如果你以一个unique_ptr开头,你可以随后通过移动转换为shared_ptr,或者通过.get()或.release()转换为另一个自定义的智能指针(甚至转换为原始指针)。
指南:希望使用标准智能指针,默认为unique_ptr,如果需要共享,则使用shared_ptr。它们是所有C ++库都能理解的常见类型。仅在需要与其他库的互操作性时使用其他智能指针类型,或者在标准指针上使用删除器和分配器无法实现的自定义行为的必要时使用。

2.为什么你几乎总是使用make_shared来创建一个由shared_ptrs拥有的对象?说明。
注意:如果需要使用自定义分配器创建对象,这很少见,您可以使用allocate_shared。注意,即使它的名称稍有不同,alloc_shared应该被视为“只是make_shared的风味,让你指定一个分配器,”所以我主要是谈论他们作为make_shared这里,而不是区别很大。

有两种主要情况,您不能使用make_shared(或allocate_shared)创建一个您知道将由shared_ptrs拥有的对象:(a)如果您需要自定义删除器,例如因为使用shared_ptrs来管理非内存资源或在非标准内存区域中分配的对象,则不能使用make_shared,因为它不支持指定删除程序;和(b)如果你采用一个原始指针指向从其他(通常是遗留)代码交给你的对象,你将直接从那个原始指针构造一个shared_ptr。

指南:使用make_shared(或者,如果你需要一个自定义分配器,allocate_shared)来创建一个对象,你知道它将由shared_ptrs拥有,除非你需要一个自定义删除器或者从其他地方采用一个原始指针。

所以,为什么使用make_shared(或者,如果你需要一个自定义分配器,allocate_shared)只要你可以,这几乎总是?有两个主要原因:简单性和效率。

首先,使用make_shared代码更简单。为了清楚和正确,首先写。

第二,使用make_shared更有效率。 shared_ptr实现必须在由引用给定对象的所有shared_ptrs和weak_ptrs共享的控制块中维护内务处理信息。特别地,该内务信息必须不仅包括一个而且包括两个引用计数:

“强引用”计数跟踪当前保持对象活动的shared_ptrs的数量。 当最后的强引用消失时,共享对象被销毁(并且可能被释放)。“弱引用”计数来跟踪当前观察对象的weak_ptrs的数目。 当最后的弱引用消失时,共享内务控制块被销毁和释放(并且共享对象被释放,如果它还没有)。如果您通过原始新表达式单独分配对象,然后将其传递给shared_ptr,则shared_ptr实现不能单独分配控制块,如示例2(a)和图2(a)所示。


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

Figure 2(a): Approximate memory layout for Example 2(a).

We’d like to avoid doing two separate allocations here. If you use make_shared to allocate the object and the shared_ptr all in one go, then the implementation can fold them together in a single allocation, as shown in Example 2(b) and Figure 2(b).

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

Figure 2(b): Approximate memory layout for Example 2(b).



注意,组合分配有两个主要优点:
它减少了分配开销,包括内存碎片。首先,最明显的做法是减少分配请求的数量,这通常是更昂贵的操作。这也有助于减少对分配器的争用(一些分配器不能很好地扩展)。第二,仅使用一个内存块而不是两个内存减少了每个分配的开销。每当你请求一块内存,系统必须给你至少这么多字节,并且通常给你一些,因为使用固定大小的池或者每个分配的内务信息。因此,通过使用单个内存块,我们倾向于减少总额外开销。最后,我们还自然地减少导致碎片的间隙之间的“死”额外的数量。
它提高了地方性。引用计数经常与对象一起使用,并且对于小对象可能在同一缓存行上,这提高了缓存性能(只要没有一些线程在紧密循环中复制智能指针;不要去做)。
和往常一样,当你可以表达更多的你想要实现的一个单一的函数调用,你给系统一个更好的机会,找出一种方法来更有效地工作。当使用对v.insert(第一个,last)的单个范围插入调用而不是对v.insert(value)的100个调用将100个元素插入向量时,这是真的,因为它使用单个调用make_shared调用新的widget()和shared_ptr(widget *)。

还有两个优点:使用make_shared避免显式新,并避免异常安全问题。这两个也适用于make_unique,所以我们将在#3下覆盖它们


3.为什么你几乎总是使用make_unique创建一个对象,最初由unique_ptr拥有?说明。
和make_shared一样,有两种情况,你不能使用make_unique来创建一个对象,你知道这个对象将被unique_ptr拥有(至少在最初):如果你需要一个自定义的删除器,或者你正在采用一个原始指针。
否则,这几乎总是,更喜欢make_unique。
指南:使用make_unique创建一个未共享的对象(至少尚未共享),除非您需要自定义删除程序或从其他地方采用原始指针。
除了具有make_shared的对称性之外,make_unique还提供至少两个其他优点。首先,你应该更喜欢使用make_unique <T>()而不是更冗长的unique_ptr <T> new T ,因为你应该避免一般的显式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?
简单来说,如果你首先分配和构造新的小部件,然后在分配或构造新的小部件时获得异常,该小部件被泄漏。 你可能会想:“嗯,我可以只是改变新的小部件到make_unique <widget>(),这个问题会消失,对吧?

sink( make_unique<widget>(),
      unique_ptr<gadget>new gadget );         // Q2: is this better?
答案是否定的,因为C ++留下未指定的函数参数的评估顺序,因此可以首先执行新的小部件或新的小部件。 如果新的小工具首先被分配和构造,那么make_unique <widget>抛出,我们有同样的问题。
但是,虽然只是更改其中一个参数使用make_unique不关闭洞,将它们改为make_unique真的完全消除了问题:
sink( make_unique<widget>(), make_unique<gadget>() );  // exception-safe
这个异常安全问题在GotW#56中有更详细的介绍。
指南:要分配一个对象,更喜欢默认编写make_unique,当你知道对象的生命周期将被使用shared_ptrs来管理时,写make_shared。

4.与auto_ptr有什么关系?
auto_ptr最有趣的特点是在C ++移动语义之前尝试创建一个unique_ptr。 auto_ptr现在已被弃用,不应在新代码中使用。
如果你在现有的代码库中有auto_ptr,当你有机会尝试全局搜索和替换auto_ptr到unique_ptr; 绝大多数的使用都是一样的,它可能暴露(作为编译时错误)或修复(默默)一个或两个你不知道你有。


以上是关于GotW #89智能指针的一些建议的主要内容,如果未能解决你的问题,请参考以下文章

智能指针-使用避坑和实现

GotW #101“解决方案”真的解决了啥问题吗?

RAII&智能指针

C99中的restrict和C89的volatilekeyword

共享智能指针shared_ptr的实现

70道指针数组笔试题