Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/157.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 使用单个函数实现复制和移动分配_C++_C++11_Move_Assignment Operator_Library Design - Fatal编程技术网

C++ 使用单个函数实现复制和移动分配

C++ 使用单个函数实现复制和移动分配,c++,c++11,move,assignment-operator,library-design,C++,C++11,Move,Assignment Operator,Library Design,通常,给定某种类型的T,要实现复制和移动分配,需要两个功能 T& operator=(T&&) { ... } T& operator=(const T&) { ... } 最近,我开始意识到,一个人就足够了 T& operator=(T v) { swap(v); return *this; } 此版本利用复制/移动构造函数。分配是复制还是移动取决于v的构造方式。这个版本甚至可能比第一个版本更快,因为pass-by-value为编译器

通常,给定某种类型的
T
,要实现复制和移动分配,需要两个功能

T& operator=(T&&) { ... }
T& operator=(const T&) { ... }
最近,我开始意识到,一个人就足够了

T& operator=(T v) {
  swap(v);
  return *this;
}
此版本利用复制/移动构造函数。分配是复制还是移动取决于
v
的构造方式。这个版本甚至可能比第一个版本更快,因为pass-by-value为编译器优化提供了更多空间[1]。那么,第一个版本比第二个版本有什么优势,即使是标准库也使用它


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

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

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

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

这就假设你不能写出实际作业的最佳版本。考虑将一个代码< >向量< /代码>复制到另一个。如果目标
向量
有足够的存储空间来保存源向量的大小。。。你不需要分配。而如果复制构造,则必须分配存储。只有这样才能释放您本可以使用的存储空间

即使在最佳情况下,复制/移动和交换也不会比使用值更有效。毕竟,您将引用该参数<代码>标准::交换对值无效。因此,无论您认为使用引用会损失多少效率,这两种方式都会损失

支持复制/移动和交换的主要参数有:

  • 减少代码重复。只有当复制/移动分配操作的实现与复制/移动构造或多或少相同时,这才是有利的。许多类型的情况并非如此;如前所述,
    vector
    可以通过尽可能使用现有存储对自身进行大量优化。事实上,许多容器可以(特别是序列容器)

  • 以最小的努力提供强大的异常保证。假设您的移动构造函数不例外

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


    这样,我就不用担心了。我的大部分类都不需要显式定义这些函数。

    我意识到这有一个公认的答案,但我觉得我必须加入。这里有两个不同的问题:

  • 统一赋值运算符。这意味着您有一个按值赋值的赋值运算符,而不是两个按常量和&&赋值的重载
  • 复制和交换(CAS)复制分配运算符
  • 如果你在做1,你通常会做2。因为您需要在某个地方实现交换/移动分配逻辑,而不能在统一分配操作符中实现它,所以通常需要实现交换并调用它。但做2并不意味着你必须做1:

    T& operator=(T&&) { /* actually implemented */ }
    T& operator=(const T& t) { T t2(t); swap(*this, t2); return *this;}
    
    在本例中,我们实现了移动分配,但使用默认交换(它执行一个移动构造和两个移动分配)

    进行CAS的动机是为了获得强有力的例外保证,尽管正如T.C.在评论中指出的,您可以:

    T& operator=(const T& t) { *this = T(t); return *this;}
    
    在我编写的大多数代码中,性能是一个问题,我从来都不需要强异常保证,所以我几乎不会这样做,所以这取决于您的用例


    你永远不应该这样做。它们最好是单独的函数,这样移动分配就可以标记为noexcept。

    这已经被打死一百万次了-例如,@T.C.我很困惑。你能详细说明一下吗?可能会发布一个答案吗?我想我链接到的霍华德·希南特的答案,他链接到的幻灯片,以及答案下面的评论都已经很好地涵盖了这个问题。简单的回答是,复制和交换的效率可能要低得多,因为复制构造可能比复制分配昂贵得多,因为后者可以重用lhs的资源。真正的答案应该是:不要这样做,因为如果这样做,就不能提供noexcept移动分配操作。我明白了。这个习惯用法一般来说是不合适的,但是在实现
    侵入式的\u ptr
    (1)的特殊情况下应该可以,也可以标记为noexcept,因为参数的构造发生在调用站点。但这对大多数程序员来说是违反直觉的。当然,当使用复制的左值调用时,整个构造(=包括客户端的“设置”)不会例外。OTOH,如果需要无异常代码,无论如何都要小心,所以这可能没那么重要。@PaulGroke它可以标记为noexcept,但问题是它是否应该标记为noexcept。我不认为这仅仅是违反直觉的,我认为这实际上是一种不好的做法。它归结为:如果泛型构造显式约束模板函子