【问题标题】:Should copy assignment operator leverage std::swap as a general rule?复制赋值运算符应该利用 std::swap 作为一般规则吗?
【发布时间】:2014-11-14 12:47:56
【问题描述】:

始终使用std::swap 实现我的复制赋值运算符是否是一种好的一般做法?我的理解是,这提供了一种共享复制构造函数实现的方法。我想避免复制实际的复制逻辑本身。所以这就是我要做的:

class Foo
{
public:
    Foo(Foo const& other) { /* assume valid implementation */ }

    Foo& operator= (Foo other)
    {
        std::swap(*this, other);
        return *this;
    }
};

将“other”传递给赋值运算符的行为执行复制构造(此时我们已经共享了复制逻辑)。我假设交换将调用移动构造(这里有一个编译器生成的实现)。

我已经对几乎每个我实现复制构造的类都这样做了,因为赋值和构造函数从来没有不同的实现。

【问题讨论】:

  • 是的,复制和交换习语对于任何拥有资源的类来说都是一个很好的习语。另一个好处是您可以从交换中获得强大的异常保证。如果更多基于意见,则作为代码库的“一般规则”,imo。
  • 您应该在swap 之前为using namespace std; 并删除std::。这样,如果可用,将使用更具体的 swap。除此之外,这是相当标准的。
  • 有些人认为用swap 来实现特殊的成员函数并不是最优的。由于swapother 带入某些特定状态,因此存在一些不必要的分配。此外,提供强异常保证的赋值运算符可能是一种悲观:用户可能不需要这种保证,但无论如何他们都会为此付出代价。
  • @dyp 好点,但我认为从设计的角度来看,稍后返回并确保异常安全是一个很大的痛苦,有时如果不讨论完整的重写,你就无法做到这一点。与本地化和许多其他基础性事情一样,最好提前做好。
  • "(这里有一个编译器生成的实现)。" - 您在示例代码中阻止了编译器生成的实现。如果您打算拥有它,请添加正确的=default

标签: c++ c++11


【解决方案1】:

您的代码没有移动构造函数。您的复制构造函数阻止了移动构造函数的自动创建,并试图移动您的类而不是复制它。

对于 move-assign,您的 operator= 也会阻止其自动执行,并且可以在其位置使用。

最终结果是an infinite recursive call of = (live code)

如果遵循 0 规则,则不需要复制构造函数、移动构造函数、移动赋值、复制赋值或析构函数。如果你写了其中任何一个,你应该准备好写所有的。

使用std::swap 可能很有用,但是因为您必须编写移动分配和移动构造,所以根据std::swap 执行任一操作都是等待发生的无限递归。

【讨论】:

  • o.O 你说得对:你不能用std::swap 来实现operator=;您可以根据自己的自己的(“优化”)swap 来实现它。运算符的常用复制和交换习语使用自定义swap(成员函数)。
  • 我想如果我只添加移动构造函数,就可以修复无限递归(顺便说一句)。移动赋值仍然是一个副本,但交换最终会使用移动构造函数,对吧?
  • @void.pointer std::swap 这样做:swap(A& a, A& b) { A tmp( move(a) ); a = move(b); b = move(tmp); } 所以它需要一个移动赋值运算符。
  • @dyp:从技术上讲,std::swap 需要 T 才能移动分配T 不需要移动赋值运算符即可移动赋值。例如,复制赋值运算符可以满足移动可赋值要求。
  • @dyp:我已经阅读了足够多的你的帖子,知道你知道这一点。只是当稍微不正确的事情被重复时,新手往往会相信它们是完全正确的。例如。复制交换习语是实现分配的最佳方式... ;-)
【解决方案2】:

如果Foo 包含非静态数据成员std::vectorstd::string,或包含包含vectorstring 的数据成员(即甚至是间接的),那么这可以是一个非常 降低代码速度的有效方法。它甚至比拨打std::sleep_for 更有效,因为后者不会浪费移动设备的电池电量。

原因是调用vectorstring 复制分配的复制分配有机会重用容器的容量。而 swap 成语 always 会丢弃容量。

更多信息,请查看我的ACCU 2014 talk, slides 43-53

请特别注意这张性能图表,它显示了调用vector 的复制赋值运算符与使用vector 数据成员执行复制/交换习语相比的速度增加。

this http://howardhinnant.github.io/accu_2014_48.pdf

最好的情况是,复制/交换习语与使用vector 的复制分配一样快(当容量永远不够用时)。在最坏的情况下(当容量总是足够时),复制/交换速度会慢近 8 倍。平均而言,复制/交换需要 70% 的速度命中(对于此测试)。

【讨论】:

  • 我认为这归结为您是否想要/需要提供强大的异常保证。但是 IIRC,您赞成 strong_assign 进行外部控制。
  • 只是链接到这个问题并进一步讨论stackoverflow.com/questions/51463356/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-27
  • 2017-11-21
  • 1970-01-01
  • 2020-06-18
相关资源
最近更新 更多