【问题标题】:Under what conditions should I be thinking about implementing a move constructor and a move operator?在什么情况下我应该考虑实现移动构造函数和移动运算符?
【发布时间】:2012-01-18 07:44:29
【问题描述】:
对于标准的复制构造函数和赋值运算符,我总是考虑实现它们或deleteing 默认值不存在,如果我的类实现析构函数。
对于新的 move 构造函数 和 move 运算符,考虑是否需要实现的正确方法是什么?
作为从 C++0x 之前的系统过渡的第一步,我可以只使用默认的 move 构造函数 和 move 运算符 delete 还是应该离开他们一个人?
【问题讨论】:
标签:
c++
constructor
c++11
rvalue-reference
【解决方案1】:
您不必担心它,因为当您用户声明一个析构函数(或 12.8/9 中列出的任何其他内容)时,它会阻止默认移动构造函数生成。因此,没有与副本相同的风险,即默认错误。
所以,作为第一关,别管他们。在您现有的代码中可能存在 C++11 移动语义允许移动的地方,而 C++03 则要求复制。您的类将继续被复制,如果这在 C++03 中没有导致性能问题,那么我无法立即想到它在 C++11 中的任何原因。如果它确实导致了 C++03 中的性能问题,那么您就有机会修复代码中以前从未遇到过的错误,但这是一个机会,而不是义务;-)
如果您稍后实现移动构造和赋值,它们将被移动,特别是如果您认为您的类的 C++11 客户端不太可能使用“交换”来避免复制,您将希望这样做,比 C++03 客户端更有可能通过值等传递您的类型。
在 C++11 中编写新类时,您需要按照您在 C++03 中考虑和实现 swap 的相同标准来考虑和实现 move。可复制的类实现了 C++11 中“可移动”的概念,(就像在 C++03 中可复制的类可以通过 std 中的默认实现进行交换),因为“可移动”不'不要说源处于什么状态 - 特别是允许它保持不变。所以副本作为移动是有效的,只是它不一定是有效的,而且对于许多职业,你会发现与“好的”移动或交换不同,副本可以抛出。
您可能会发现,如果您有析构函数(因此没有默认的移动构造函数),并且您还有一个可移动但不可复制的数据成员(因此也没有默认的复制构造函数),您可能会发现您必须为您的类实现移动)。这就是移动在语义和性能方面变得重要的时候。
【解决方案2】:
由于库的编写方式,使用 C++11,您很少需要提供析构函数或复制语义。编译器提供的成员几乎总是可以正常工作(前提是它们被正确实现:MSVC 迫使您手动实现很多移动语义,这非常麻烦)。
如果您必须实现自定义析构函数,请使用以下方法:
- 实现一个移动构造函数和一个按值取值的赋值运算符(使用复制和交换:请注意,您不能使用
std::swap,因为它使用了赋值。您必须自己提供一个私有交换)。注意异常保证(查找std::move_if_noexcept)。
-
如有必要,实现一个复制构造函数。否则,删除它。请注意,非默认复制语义很少有意义。
此外,虚拟析构函数也算作自定义析构函数:在声明虚拟析构函数时提供或删除复制 + 移动语义。
【解决方案3】:
由于它们被用作优化,如果优化适用于您的类,您应该实施它们。如果您可以从即将被销毁的临时对象中“窃取”您的类所持有的内部资源。 std::vector 是一个完美的例子,其中移动构造函数只分配指向内部缓冲区的指针,而使临时对象为空(有效地窃取元素)。