【问题标题】:cpp make_shared for void pointerscpp make_shared 用于 void 指针
【发布时间】:2012-01-28 13:42:46
【问题描述】:

我想使用 std::make_shared 创建一个 void 指针。由于 make_shared 应该比 shared_ptr(new T) 更快,并且异常保存我想知道是否有一个库函数可以在 make_shared 方式中创建一个 shared_ptr(new foo)。

【问题讨论】:

  • 它会创建什么类型的对象?
  • 这是一个问题,必须有一些不同的语法来提供它,例如make_void_shared(构造函数参数)。我没有从 static_pointer_cast(make_shared(bar1,...,barn)) 获得性能提升
  • 你要求一个 shared_ptr 来包装一个 void* ,它又指向一个真实的对象?但这有什么意义呢?
  • 在进行类型擦除时不必进行内存管理(shared_ptr)(据我所知,类型擦除的概念)
  • 也许你正在寻找例如boost::any?

标签: c++ c++11 smart-pointers


【解决方案1】:

您可以将任何shared_ptr<foo> 转换为shared_ptr<void> 而不会损失与make_shared 相关的效率:

#include <memory>

struct foo {};

int main()
{
    std::shared_ptr<void> p = std::make_shared<foo>();
}

转换将foo 和引用计数保持在相同的内存分配中,即使您现在通过void* 引用它。

更新

这是如何工作的?

std::shared_ptr&lt;foo&gt; 的一般结构是两个指针:

                          +------> foo
                          |         ^
p1  ---------> (refcount, +)        |
p2  --- foo* -----------------------+

p1 指向一个控制块,其中包含一个引用计数(实际上是两个引用计数:一个用于强所有者,一个用于弱所有者)、一个删除器、一个分配器和一个指向对象“动态”类型的指针. “动态”类型是shared_ptr&lt;T&gt; 构造函数看到的对象的类型,比如Y(可能与T 相同也可能不同)。

p2 具有 T* 类型,其中 Tshared_ptr&lt;T&gt; 中的 T 相同。将此视为存储对象的“静态”类型。当您取消引用 shared_ptr&lt;T&gt; 时,将取消引用 p2。当您销毁 shared_ptr&lt;T&gt; 时,如果引用计数变为零,则控制块中的指针有助于销毁 foo

在上图中,控制块和foo都是动态分配的。 p1是一个拥有指针,控制块中的指针是一个拥有指针。 p2 是一个非拥有指针。 p2only 函数是取消引用(箭头运算符、get() 等)。

当您使用make_shared&lt;foo&gt;() 时,实现有机会将foo 与引用计数和其他数据一起放在控制块中:

p1  ---------> (refcount, foo)
p2  --- foo* --------------^

这里的优化是现在只有一个分配:现在嵌入foo的控制块。

当上述内容转换为shared_ptr&lt;void&gt; 时,会发生以下情况:

p1  ---------> (refcount, foo)
p2  --- void* -------------^

p2 的类型从 foo* 更改为 void*。而已。 (除了增加/减少引用计数以解释临时的副本和销毁——这可以通过从右值构造来省略)。当引用计数变为零时,仍然是控制块破坏了通过p1 找到的foop2 不参与销毁操作。

p1 实际上指向控制块的通用基类。这个基类不知道存储在派生控制块中的foo 类型。当实际对象类型Y 已知时,派生控制块在shared_ptr 的构造函数中构造。但从那时起shared_ptr 只能通过control_block_base* 与控制块通信。所以像销毁这样的事情是通过虚函数调用发生的。

在 C++11 中从右值 shared_ptr&lt;foo&gt; 中“移动构造”shared_ptr&lt;void&gt; 只需复制两个内部指针,而不必操作引用计数。这是因为右值 shared_ptr&lt;foo&gt; 无论如何都会消失:

// shared_ptr<foo> constructed and destructed within this statement
std::shared_ptr<void> p = std::make_shared<foo>();

这在shared_ptr构造函数源代码中可以最清楚地看到:

template<class _Tp>
template<class _Yp>
inline _LIBCPP_INLINE_VISIBILITY
shared_ptr<_Tp>::shared_ptr(shared_ptr<_Yp>&& __r,
                            typename enable_if<is_convertible<_Yp*, _Tp*>::value, __nat>::type)
         _NOEXCEPT
    : __ptr_(__r.__ptr_),
      __cntrl_(__r.__cntrl_)
{
    __r.__ptr_ = 0;
    __r.__cntrl_ = 0;
}

在转换构造之前,引用计数只有 1。在转换构造之后,引用计数仍然是 1,而在其析构函数运行之前,源指向空。简而言之,这就是移动语义的乐趣! :-)

【讨论】:

  • 这是如何工作的?使用编译器优化(讨厌依赖那些)?我的理解告诉我,对于纯 C++,我能期望的最好的就是移动语义。阅读本文后,我预计会发生更复杂的事情。
  • 非常详细的优秀答案!似乎留下了一些开销(正如我所担心的那样)(引用计数增加,新的共享指针 p2 的类型为 void,共享指针的释放(指向控制块的指针,指向对象的指针),其中 p2 是实际类型)似乎虽然留下来。如果我错了,请纠正我。
  • @ted:如果不转换为shared_ptr&lt;void&gt;,您将使用RVO(返回值优化)创建p,这意味着没有副本,没有引用计数操作。在 C++11 中,转换为 shared_ptr&lt;void&gt; 几乎是免费的,但并非完全免费。 shared_ptr&lt;foo&gt; 仍然是通过 RVO 创建的,但它是一个右值。然后优化从这个右值构造shared_ptr&lt;void&gt; 以简单地复制右值源的两个内部指针并将两个内部指针归零。这不会触及引用计数。 2 次加载和 4 次存储(都是非原子的)是额外的成本。
  • 感谢您的深入解释。
猜你喜欢
  • 2014-03-14
  • 1970-01-01
  • 2014-05-30
  • 2021-01-04
  • 2017-11-03
  • 2013-12-18
  • 1970-01-01
  • 1970-01-01
  • 2014-09-08
相关资源
最近更新 更多