我想使用std::make_shared创建一个空指针。由于make_shared应该比shared_ptr(new T)快,并且可以保存异常,所以我想知道是否有一个库函数以make_shared的方式创建shared_ptr(new foo)。
最佳答案
您可以将任何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<foo>
的一般结构是两个指针: +------> foo
| ^
p1 ---------> (refcount, +) |
p2 --- foo* -----------------------+
p1
指向包含引用计数(实际上是两个引用计数:一个用于强所有者,一个用于弱所有者),删除器,分配器和指向对象“动态”类型的指针的控制块。 “动态”类型是shared_ptr<T>
构造函数看到的对象的类型,例如Y
(可能与T
相同或不同)。p2
具有T*
类型,其中T
与T
中的shared_ptr<T>
相同。可以将其视为存储对象的“静态”类型。取消引用shared_ptr<T>
时,将取消引用p2
。当您破坏shared_ptr<T>
时,并且如果引用计数变为零,则是控制块中的指针有助于破坏foo
。在上图中,控制块和
foo
都是动态分配的。 p1
是拥有的指针,控制块中的指针是拥有的指针。 p2
是非所有者指针。 p2
的仅函数被取消引用(箭头运算符,get()
等)。当您使用
make_shared<foo>()
时,实现可以将foo
与引用计数和其他数据一起放入控制块中:p1 ---------> (refcount, foo)
p2 --- foo* --------------^
这里的优化是现在只有一个分配:现在嵌入
foo
的控制块。当上述转换为
shared_ptr<void>
时,所有发生的是:p1 ---------> (refcount, foo)
p2 --- void* -------------^
即
p2
的类型从foo*
变为void*
。而已。 (除了增加/减少引用计数以外,还考虑了临时副本的复制和销毁-可以通过构造从右值中消除它)。当引用计数变为零时,仍然是控制块破坏了通过foo
找到的p1
。 p2
不参与销毁操作。p1
实际上指向控制块的通用基类。该基类不知道存储在派生控制块中的foo
类型。在知道实际对象类型shared_ptr
时,将在Y
的构造函数中构造派生的控制块。但是从那时起,shared_ptr
只能通过control_block_base*
与控制块进行通信。因此,诸如破坏之类的事情是通过虚函数调用发生的。从C++ 11中的右值
shared_ptr<void>
中“移动构造”一个shared_ptr<foo>
只需复制两个内部指针,而不必操纵引用计数。这是因为右值shared_ptr<foo>
无论如何都会消失:// 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++ - cpp make_shared用于无效指针,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/8645835/