gcc shared_ptr 复制赋值实现

gcc shared_ptr copy assignment implementation

我正在扫描 GCC 5 中的 shared_ptr 实现,我看到了以下内容:

  __shared_ptr&
  operator=(__shared_ptr&& __r) noexcept
  {
    __shared_ptr(std::move(__r)).swap(*this);
    return *this;
  }

我的问题是为什么在 交换之前临时 的附加移动构造?我假设编译会消除任何额外的开销——但为什么不直接调用 __r.swap(*this)?我是否缺少一些聪明的副作用?

我看到 class 中的其他函数也使用相同的模式实现,我可以理解接受 const 引用但接受右值引用的情况?

因为我们需要在引用的右值上调用析构函数以减少实例计数。

首先,这就是标准所说的,GCC 完全遵循它。

这样赋值运算符有一个后置条件__r.empty(),你的建议无法实现,所以按照你的建议实施它会对标准所说的产生不同的影响,因此是非符合。

即这个断言成立:

auto p1 = std::make_shared<int>(1);
auto p2 = std::make_shared<int>(2);
p1 = std::move(p2);
assert( !p2 );

"clever side effect"是你创建了一个新的emptyshared_ptr,它在交换后最终保留了*this的旧值,然后超出范围。这意味着 *this 的旧值不会在 __r.

中结束

通过将 __r 的内脏移动到作为函数 returns 销毁的临时对象中,确保移出的对象 __r 指向处于空状态。我猜他们想把这个逻辑放在一个地方,移动构造函数,看起来像这样。

__shared_ptr(__shared_ptr&& __r) noexcept
  : _M_ptr(__r._M_ptr), _M_refcount()
{
  _M_refcount._M_swap(__r._M_refcount);
  __r._M_ptr = 0;
}

就我个人而言,我更喜欢以下实现,据我所知,它是等效的。

widget&
operator=(widget&& other) noexcept
{
  widget temp {};
  swap(*this, temp);
  swap(*this, other);
  return *this;
}

它可以用这样实现的移动构造函数来补充。

widget(widget&& other) noexcept : widget {}
{
  swap(*this, other);
}

我假设有一个

void
swap(widget&, widget&) noexcept;

ADL 可以找到的重载以及 widget 的默认构造函数是 noexcept