是否存在std :: shared_ptr的非原子等价物?为什么<memory>中没有一个?

Cor*_*lks 82 c++ smart-pointers c++11

这是一个两部分问题,所有关于原子性std::shared_ptr:

1. 据我所知,std::shared_ptr是唯一的智能指针<memory>这就是原子.我想知道是否有非原子版本std::shared_ptr可用(我看不到任何内容<memory>,所以我也接受标准之外的建议,比如Boost中的建议).我知道boost::shared_ptr也是原子的(如果BOOST_SP_DISABLE_THREADS没有定义),但也许有另一种选择?我正在寻找具有相同语义std::shared_ptr但没有原子性的东西.

2.我明白为什么std::shared_ptr是原子的; 这有点好.然而,对于每种情况来说都不是很好,而且C++历来有"只为你使用的东西买单"的口号.如果我没有使用多个线程,或者我使用多个线程但是没有跨线程共享指针所有权,则原子智能指针是过度的.我的第二个问题是为什么不是std::shared_ptrC++ 11 中提供的非原子版本?(假设有一个原因)(如果答案是简单的"非原子版本根本不会考虑"或"从来没有人问非原子版"这很好!).

对于问题#2,我想知道是否有人提出过非原子版shared_ptr(无论是对Boost还是标准委员会)(不是替换原子版本shared_ptr,而是与它共存)并且它被击落了具体原因.

How*_*ant 101

我想知道是否有非原子版的std :: shared_ptr可用

标准不提供.可能有一个由"第三方"图书馆提供.实际上,在C++ 11之前,以及Boost之前,似乎每个人都编写了自己的引用计数智能指针(包括我自己).

2.我的第二个问题是为什么C++ 11中没有提供std :: shared_ptr的非原子版本?

这个问题在2010年的拉珀斯维尔会议上进行了讨论.该主题由瑞士国家机构评论#20介绍.辩论的双方都有强烈的争论,包括你在问题中提供的论点.但是,在讨论结束时,投票绝大多数(但并非一致)反对添加非同步(非原子)版本shared_ptr.

反对的论点包括:

  • 使用未同步的shared_ptr编写的代码最终可能会在未来的线程代码中使用,最终导致难以调试的问题而没有警告.

  • 让一个"通用"shared_ptr成为引用计数流量的"单向"有好处:从原始提案中:

    无论使用哪种功能,都具有相同的对象类型,极大地促进了库(包括第三方库)之间的互操作性.

  • 原子的成本虽然不是零,但并非压倒性的.通过使用不需要使用原子操作的移动构造和移动分配来减轻成本.这种操作通常用于vector<shared_ptr<T>>擦除和插入.

  • 没有什么能阻止人们编写他们自己的非原子引用计数智能指针,如果这真的是他们想要做的.

那天拉珀斯维尔LWG的最后一句话是:

拒绝CH 20.目前没有达成共识进行更改.

  • 哇,完美,谢谢你的信息!这正是我希望找到的那种信息. (6认同)

Jon*_*ely 49

Howard已经很好地回答了这个问题,Nicol对于使用单一标准共享指针类型的好处提出了一些好处,而不是许多不兼容的指针类型.

虽然我完全同意委员会的决定,但我认为在特殊情况下使用非同步shared_ptr类型会有一些好处,所以我已经多次调查了这个主题.

如果我没有使用多个线程,或者我使用多个线程但是没有跨线程共享指针所有权,则原子智能指针是过度的.

使用GCC时,如果您的程序不使用多个线程,则shared_ptr不会将原子操作用于refcount.这是通过包装器函数更新引用计数来完成的,这些函数检测程序是否是多线程的(在GNU/Linux上这只是通过检测程序是否链接来完成libpthread.so)并相应地调度到原子或非原子操作.

多年前我意识到,由于GCC shared_ptr<T>__shared_ptr<T, _LockPolicy>基于类的实现,因此即使在多线程代码中,也可以通过显式使用将基类与单线程锁定策略一起使用__shared_ptr<T, __gnu_cxx::_S_single>.不幸的是因为那不是一个预期的用例,它在GCC 4.9之前并没有完全发挥作用,并且一些操作仍然使用了包装器函数,因此即使您已明确请求_S_single策略,也会调度到原子操作.有关详细信息,请参阅http://gcc.gnu.org/ml/libstdc++/2007-10/msg00180.html上的第(2)点,以及GCC的补丁,以便即使在多线程应用程序中也可以使用非原子实现.我多年来一直坐在那个补丁上,但我最终为GCC 4.9提交了它,它允许你使用这样的别名模板来定义一个非线程安全的共享指针类型,但速度稍快:

template<typename T>
  using shared_ptr_unsynchronized = std::__shared_ptr<T, __gnu_cxx::_S_single>;
Run Code Online (Sandbox Code Playgroud)

这种类型不能与之互操作,std::shared_ptr<T>并且只有在保证shared_ptr_unsynchronized在没有用户提供的同步的情况下永远不会在线程之间共享对象时才能安全使用.

这当然是完全不可移植的,但有时候还可以.使用正确的预处理器黑客,如果shared_ptr_unsynchronized<T>是别名shared_ptr<T>,你的代码仍可以正常使用其他实现,它只会比GCC快一点.

如果您在4.9之前使用GCC,则可以通过向_Sp_counted_base<_S_single>您自己的代码添加显式特化来使用它(并确保没有实例化__shared_ptr<T, _S_single>而不包括特化,以避免ODR违规.)添加此类std类型的特化在技术上是未定义的,但是在实践中工作,因为在这种情况下,我将专业化添加到GCC或您将它们添加到您自己的代码之间没有区别.

  • 只是想知道,您的模板别名示例中是否有错字?即我认为应该读取shared_ptr_unsynchronized = std :: __ shared_ptr &lt;。顺便说一句,我今天结合std :: __ enable_shared_from_this和std :: __ weak_ptr对此进行了测试,它似乎工作得很好(gcc 4.9和gcc 5.2)。我将对其进行概要分析/反汇编,以查看是否确实跳过了原子操作。 (2认同)

Nic*_*las 22

我的第二个问题是为什么不是C++ 11中提供的std :: shared_ptr的原子版本?(假设有一个原因).

人们可以很容易地问为什么不存在侵入式指针,或者可能存在的任何其他可能的共享指针变体.

从Boost传下来的shared_ptr的设计一直是创建智能指针的最低标准语言.一般来说,你可以把它从墙上拉下来并使用它.它可以在各种应用程序中普遍使用.你可以把它放在一个界面中,而且好的人会愿意使用它.

线程化将在未来变得更加普遍.实际上,随着时间的推移,线程通常是实现性能的主要手段之一.要求基本智能指针完成支持线程所需的最低限度,这有助于实现这一目标.

将六个智能指针与它们之间的微小差异转移到标准中,或者更糟糕的是基于策略的智能指针,将会非常糟糕.每个人都会选择他们最喜欢的指针并放弃所有其他指针.没有人能够与其他任何人沟通.它就像C++字符串的当前情况,每个人都有自己的类型.更糟糕的是,因为与字符串的互操作比智能指针类之间的互操作容易得多.

Boost,以及委员会的扩展,选择了一个特定的智能指针来使用.它提供了良好的功能平衡,并在实践中广泛和普遍使用.

std::vector在一些极端情况下,与裸阵列相比,效率低下.它有一些局限性; 一些用途确实希望对a的大小有一个硬性限制vector,而不使用throw分配器.然而,委员会并没有vector为每个人设计一切.它被设计为大多数应用程序的良好默认值.那些无法工作的人可以写一个满足他们需求的替代方案.

正如你可以为一个智能指针,如果shared_ptr的原子性是一个负担.然后,人们也可能会考虑不要复制它们.

  • 为"一个人也可能考虑不要将它们复制到那么多". (7认同)

The*_*ist 9

Boost 提供了一个shared_ptr非原子的。它的名字叫local_shared_ptr,可以在 boost 的智能指针库中找到。

  • @Red.Wave如果你查看实际的源代码https://github.com/boostorg/smart_ptr/blob/aa1341a6a27bd5ceeca1ace990b9d2c76eb49247/include/boost/smart_ptr/local_shared_ptr.hpp#L120你会发现没有双重间接。文档中的这一段只是一个心理模型。 (3认同)

小智 5

我正在准备一个关于工作中的shared_ptr 的演讲。我一直在使用修改后的 boost shared_ptr ,避免单独的 malloc (就像 make_shared 可以做的那样)和用于锁定策略的模板参数,如上面提到的 shared_ptr_unsynchronized 。我正在使用该程序

http://flyingfrogblog.blogspot.hk/2011/01/boosts-sharedptr-up-to-10-slower-than.html

作为测试,在清理不必要的shared_ptr副本之后。该程序仅使用主线程并显示测试参数。测试环境是运行 linuxmint 14 的笔记本。以下是所用时间(以秒为单位):

测试运行设置 boost(1.49) std 并使用 make_shared 修改 boost
mt-不安全(11) 11.9 9/11.5(-pthread on) 8.4  
原子(11) 13.6 12.4 13.0  
mt-不安全(12) 113.5 85.8/108.9(-pthread on) 81.5  
原子(12) 126.0 109.1 123.6  

只有“std”版本使用 -std=cxx11,并且 -pthread 可能会在 g++ __shared_ptr 类中切换 lock_policy。

从这些数字中,我看到了原子指令对代码优化的影响。该测试用例不使用任何 C++ 容器,但vector<shared_ptr<some_small_POD>>如果对象不需要线程保护,则可能会受到影响。Boost 受到的影响较小,因为额外的 malloc 限制了内联和代码优化的数量。

我还没有找到一台具有足够内核的机器来对原子指令的可扩展性进行压力测试,但仅在必要时使用 std::shared_ptr 可能会更好。