这是一个基本问题,但我没有找到相关的以前的帖子。以下问题的标题听起来可能和我的问题相同,但问题本身与标题不符:使用shared_ptr.reset还是operator =更好?
我对
reset()
的 std::shared_ptr
成员函数的用途感到困惑:除了赋值运算符之外,它还有什么作用?
具体来说,给出定义:
auto p = std::make_shared<int>(1);
以下两行是否等效:
p = std::make_shared<int>(5);
p.reset(new int(5));
这些怎么样:
p = nullptr;
p.reset();
如果这两条线在两种情况下都是等价的,那么
reset()
的目的是什么?
编辑:让我重新表述这个问题以更好地强调其要点。问题是:是否存在
reset()
让我们实现一些没有它就不容易实现的事情?
使用
reset()
时,传递给reset的参数不必是托管对象(也不能是);而使用 =
时,右侧必须是托管对象。
所以这两行给你相同的最终结果:
p = std::make_shared<int>(5); // assign to a newly created shared pointer
p.reset(new int(5)); // take control of a newly created pointer
但我们不能这样做:
p = new int(5); // compiler error no suitable overload
p.reset(std::make_shared<int>(5).get()); // uh oh undefined behavior
如果没有
reset()
,您将无法在不创建共享指针并分配它的情况下将共享指针重新分配给不同的原始指针。如果没有 =
,您将无法使共享指针指向另一个共享指针。
在某些情况下,
reset
可以避免动态内存分配。考虑一下代码
std::shared_ptr<int> p{new int{}}; // 1
p.reset(new int{}); // 2
在第 1 行,发生了 2 个动态内存分配,一个用于
int
对象,第二个用于 shared_ptr
的控制块,该控制块将跟踪对托管对象的强/弱引用的数量。
在第 2 行,再次为新的
int
对象进行动态内存分配。在 reset
的主体内,shared_ptr
将确定没有其他对先前管理的 int
的强引用,因此它必须 delete
它。由于也没有任何弱引用,它也可以取消分配控制块,但在这种情况下,谨慎的做法是重用相同的控制块,否则无论如何都必须分配一个新的控制块。
如果您总是必须使用赋值,则上述行为是不可能的。
std::shared_ptr<int> p{new int{}}; // 1
p = std::shared_ptr<int>{new int{}}; // 2
在这种情况下,第 2 行对
shared_ptr
构造函数的第二次调用已经分配了一个控制块,因此 p
将必须释放其自己的现有控制块。
我不会包括你的第一个子问题背后的基本原理,即通过
make_shared
或指针构造之间的差异,因为这种差异在几个不同的位置突出显示,包括这个优秀的问题。
但是,我认为区分使用
reset
和operator=
是有建设性的。前者放弃由 shared_ptr
管理的资源的所有权,如果 shared_ptr
恰好是唯一所有者,则通过销毁它,或者通过减少引用计数。后者意味着与另一个shared_ptr
共享所有权(除非您要搬家)。
正如我在评论中提到的,传递给
reset
的指针不属于另一个共享或唯一指针,这一点很重要,因为它会在两个独立管理器销毁时产生未定义的行为 - 它们都会尝试 delete
资源。
reset
的一个用例可能是共享资源的延迟初始化。您只希望 shared_ptr
管理某些资源,例如内存,如果您确实需要的话。进行彻底分配,例如:
std::shared_ptr<resource> shared_resource(new resource(/*construct a resource*/));
如果实际上从未需要它,可能会造成浪费。要通过延迟初始化来做到这一点,可以应用类似的方法:
std::shared_ptr<resource> shared_resource;
void use_resource()
{
if(!shared_resource)
{
shared_resource.reset(new resource(...));
}
shared_resource->do_foo();
}
在这种情况下使用
reset
比执行 swap
或分配给临时 shared_ptr
更简洁。
reset()
更改现有shared_ptr
的托管对象。
p = std::shared_ptr(new int(5));和 p.reset(new int(5));
前者涉及创建一个新的
shared_ptr
并将其移动到变量中。后者不会创建新对象,它只是更改由 shared_ptr
管理的底层指针。
换句话说,这两者应该在不同的情况下使用。赋值适用于当您有
shared_ptr
时,而 reset
则适用于您有原始指针时。
另一件事要记住的是,
shared_ptr
在移动分配存在之前就可以在Boost中使用,并严重影响了最新版本。无需移动分配,无需复制即可更改 shared_ptr
是有益的,因为它可以节省额外对象的簿记工作。