使用单个函数实现复制和移动赋值

Lin*_*gxi 7 c++ move library-design assignment-operator c++11

通常,给定某种类型T,要实现复制和移动赋值,需要两个函数

T& operator=(T&&) { ... }
T& operator=(const T&) { ... }
Run Code Online (Sandbox Code Playgroud)

最近,我逐渐意识到单一的就足够了

T& operator=(T v) {
  swap(v);
  return *this;
}
Run Code Online (Sandbox Code Playgroud)

此版本利用了复制/移动构造函数.赋值是复制还是移动取决于v构造方式.这个版本甚至可能比第一个版本更快,因为pass-by-value允许更多的空间用于编译器优化[1].那么,即使标准库使用它,第一个版本优于第二个版本的优势是什么?

[1]我想这解释了为什么标签和函数对象在标准库中按值传递.

Nic*_*las 5

std::swap通过执行移动构造,然后执行两个移动分配操作来实现。因此,除非您实现自己的swap操作来代替标准提供的操作,否则所提供的代码将是一个无限循环。

因此,您既可以实现2 operator=种方法,也可以实现一种operator=方法和一种swap方法。就调用的函数数量而言,它最终是相同的。

此外,您的版本operator=有时效率较低。除非省略参数的构造,否则将通过从调用者的值进行复制/移动来完成该构造。接下来是1次移动构造和2次移动分配(或您swap所做的任何操作)。适当的operator=重载可以直接与给定的参考一起使用。

并且这假设您不能编写实际分配的最佳版本。考虑将一个副本分配vector给另一个副本。如果目标vector有足够的存储空间来容纳源向量的大小,则无需分配。而如果您复制construct,则必须分配存储空间。只有这样才能释放您本可以使用的存储空间。

即使在最佳情况下,您的复制/移动和交换也不会比使用值有效。毕竟,您将参考该参数。std::swap在价值上不起作用。因此,无论您认为使用引用会损失多少效率,都会以任何一种方式丢失。

支持复制/移动和交换的原理参数是:

  1. 减少代码重复。仅当您执行复制/移动分配操作的实现与复制/移动构造大致相同时,这才是有利的。这在许多类型中都是不正确的。如前所述,vector可以在可能的情况下通过使用现有存储来进行自我优化。实际上,许多容器都可以(尤其是顺序容器)。

  2. 以最小的努力提供强大的异常保证。假设您的move构造函数也不例外。

就个人而言,我宁愿完全避免这种情况。我更喜欢让编译器生成我所有的特殊成员函数。而且,如果某个类型绝对需要我编写那些特殊的成员函数,则此类型将尽可能少。也就是说,唯一的目的就是管理需要执行此操作的任何内容。

这样,我不必担心。我的班级中绝大部分不需要明确定义这些功能。