【问题标题】:std::shared_ptr: reset() vs. assignmentstd::shared_ptr: reset() 与赋值
【发布时间】:2015-10-04 23:37:29
【问题描述】:

这是一个基本问题,但我没有找到以前的帖子。以下问题的标题听起来可能和我的问题一样,但问题本身与标题不符:is it better to use shared_ptr.reset or operator =?

我对@9​​87654323@ 的reset() 成员函数的用途感到困惑:除了赋值运算符之外,它还有什么作用?

具体来说,给出定义:

auto p = std::make_shared<int>(1);
  1. 以下两行是否等效:

    p = std::make_shared<int>(5);
    p.reset(new int(5));
    
  2. 这些呢:

    p = nullptr;
    p.reset();
    

如果这两行在两种情况下都是等价的,那么reset()的目的是什么?


编辑:让我重新措辞这个问题以更好地强调它的观点。问题是:有没有一种情况,reset() 让我们实现了没有它就不容易实现的事情?

【问题讨论】:

  • @MeirGoldenberg,在shared_ptr 上调用reset 会将托管对象更改为由指针提供的对象,并且它不应该由任何其他shared_ptr 拥有。而operator= 意味着与另一个shared_ptr 共享所有权(当然,除非您正在执行移动分配)。
  • “不应该被拥有”?这是强制执行的还是只是一个协议?如果这个评论连同一个例子成为一个完整的回复,那就太好了。
  • 这归结为从原始指针构造shared_ptr 和使用make_shared 之间的区别,这应该很容易找出。提示:存在差异,有时可能很重要。
  • @MeirGoldenberg,它由未定义的行为强制执行(如果您可以考虑“强制执行”)。考虑一下如果你newed 了一些内存并在两个不同的 shared_ptrs 上调用reset 会发生什么?当两者超出范围时,他们会尝试删除同一个块 - 因此是 UB。
  • @UlrichEckhardt 在这两种情况下我都可以使用原始指针:p = std::shared_ptr&lt;int&gt;(new int(5));p.reset(new int(5)); 原样。现在有什么不同?

标签: c++ c++11 shared-ptr


【解决方案1】:

当使用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() 让我实现了没有它就不容易实现的目标?
  • make_shared 创建的共享ptr 与.reset(new T) 创建的共享ptr 不同。
  • 有什么不同?
  • @MaxNoe 使用make_shared 指针,共享状态可以通过一次调用new 分配到单个对象中。当您进行重置时,必须将其拆分为两个单独的分配。
【解决方案2】:

我不会包含您的第一个子问题背后的基本原理,即通过make_shared 或指针构造之间的区别,因为这种区别在几个不同的位置突出显示,包括this excellent question

但是,我认为区分使用resetoperator= 是有建设性的。前者放弃由shared_ptr 管理的资源的所有权,如果shared_ptr 恰好是唯一所有者,则通过销毁它,或者通过减少引用计数。后者意味着与另一个 shared_ptr 共享所有权(除非您正在移动构建)。

正如我在 cmets 中提到的,重要的是传递给 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 更简洁。

【讨论】:

  • “后者 [即分配] 意味着与另一个 shared_ptr 共享所有权”。我认为您的意思是将其与reset() 进行对比,这使p 成为非托管资源的所有者,根据定义,这意味着唯一的所​​有权。我读得越多,我就越想知道:谁会使用reset()?是否有使用此工具的示例?
  • @MeirGoldenberg,更新了一个潜在的例子!
  • 我不太确定为什么您的示例不能像使用赋值运算符一样轻松完成。
  • shared_resource = std::make_shared&lt;resource&gt;(...); 对我来说似乎很简洁。
【解决方案3】:

reset() 更改现有shared_ptr 的托管对象。

p = std::shared_ptr(new int(5));和 p.reset(new int(5));

前者涉及创建一个新的shared_ptr 并将其移动到一个变量中。后者不会创建新对象,它只是更改由shared_ptr 管理的底层指针。

换句话说,这两者旨在用于不同的情况。当你有一个 shared_ptrreset 当你有一个原始指针时,赋值。

要记住的另一件事是,shared_ptr 在移动分配存在之前就已在 boost 中可用,并严重影响了最新版本。无需移动分配即可更改 shared_ptr 而无需复制是有益的,因为它可以节省额外对象的簿记。

【讨论】:

  • shared_ptr 只存在于 C++11 之后的标准中:en.cppreference.com/w/cpp/memory/shared_ptr。 Boost 确实有 C++03 shared_ptr
  • @NathanOliver:我的底线越界了,为了避免这种说法,重新措辞,但仍然提到它可能会产生影响。 (尽管在不复制/移动的情况下进行逻辑修改的一般能力在任何一种情况下都很常见)
【解决方案4】:

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 创建原始 shared_ptr 时这将不适用?
  • @NirFriedman 不太可能,但也许并非完全不合理。您可以想象一个库实现基于sizeof(ControlBlock) + sizeof(T) 做出决定,避免动态分配更值得。然后它可以销毁对象,但不能释放控制块并获得新指针的所有权。
  • 一般来说,我认为reset 的使用会鼓励不良做法,应该避免,但我认为这是一个正确的答案。在某些情况下,使用reset 是一种性能优化,但我会在进行更改之前检查改进是否可衡量。
  • @ChrisDrew 是的,测量很重要,因为在更大的范围内,reset 更快一点并不明显,因为 make_shared 在单个分配中创建控制块和对象,这意味着在使用时cache-locality 可能导致make_shared 胜过reset,具体取决于实际使用结果shared_ptr 的方式。换句话说,仅查看修改 shared_ptr 的性能是不够的,而是查看修改 + 使用的综合性能影响。 (这排除了没有任何实际使用场景的小型通用测试)。
  • 值得注意的是libc++libstdc++都通过构造一个新的shared_ptr(连同一个新的控制块)并与*this交换来实现reset
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-17
  • 2014-03-25
  • 2011-09-27
  • 2014-06-08
  • 2012-06-11
  • 1970-01-01
相关资源
最近更新 更多