C++ 移动赋值运算符:我想对 POD 类型使用 std::swap 吗?

C++ Move assignment operator: Do I want to be using std::swap with POD types?

从 C++11 开始,当使用移动赋值运算符时,我是否应该 std::swap 我的所有数据,包括 POD 类型?我想这对下面的示例没有影响,但我想知道普遍接受的最佳做法是什么。

示例代码:

class a
{

     double* m_d;
     unsigned int n;

public:

     /// Another question: Should this be a const reference return?
     const a& operator=(a&& other)
     {
         std::swap(m_d, other.m_d); /// correct
         std::swap(n, other.n); /// correct ?
         /// or
         // n = other.n;
         // other.n = 0;
     }
}

您可能想考虑以下形式的构造函数:- 即:总是有 "meaningful" 或定义的值存储在 nm_d.

a() : m_d(nullptr), n(0)
{
}

不,如果效率有任何问题,请不要交换 PODs。与正常分配相比没有任何好处,它只会导致不必要的副本。还要考虑是否需要将从 POD 移动到 0。

我什至不会交换指针。如果这是一种拥有关系,请使用 unique_ptr 并从中移动,否则将其视为 POD(复制它并随后将其设置为 nullptr 或您的程序逻辑需要的任何内容)。

如果您不必将 PODs 设置为零并且使用智能指针,您甚至根本不必实现移动运算符。

关于你问题的第二部分: 正如 Mateusz 已经说过的,赋值运算符应该总是 return 一个普通的(非常量)引用。

should I std::swap all my data

一般不会。移动语义可以使事情变得更快,交换直接存储在对象中的数据通常比复制它慢,并且可能为一些移出的数据成员分配一些值。

针对您的特定场景...

class a
{
     double* m_d;
     unsigned int n; 

...仅考虑数据成员来了解什么是有意义的是不够的。例如,如果您对非 POD 成员使用假设的交换组合,否则使用分配...

     std::swap(m_d, other.m_d);
     n = other.n;
     other.n = 0;

...在移动构造函数或赋值运算符中,如果说析构函数在 n0 时跳过删除 m_d,那么它可能仍然会使您的程序状态无效,或者如果它在用指向新分配的内存的指针覆盖 m_d 之前检查了 n == 0,则旧内存可能会泄漏。您必须决定 class 不变量:m_dn 的有效关系,以确保您的移动构造函数 and/or 赋值运算符使状态对未来操作有效。 (大多数情况下,被移出对象的析构函数可能是唯一留给 运行 的东西,但程序重用移出对象是有效的——例如,为它分配一个新值并在下一个处理它一个循环的迭代....)

另外,如果你的不变量允许非 nullptr m_dn == 0,那么交换 m_ds 是有吸引力的,因为它使移出的对象正在进行控制移动到的对象可能拥有的任何缓冲区:这可能会节省以后分配缓冲区的时间;平衡那个专业人士,如果以后不需要缓冲区,那么您将其分配的时间超过了必要的时间,并且如果它不够大,您最终将删除并新建一个更大的缓冲区,但至少您对此很懒惰这往往有助于提高性能(但如果你必须关心的话)。

我认为应该这样重写。

class a
{
public:
     a& operator=(a&& other)
     {
         delete this->m_d; // avoid leaking
         this->m_d = other.m_d;
         other.m_d = nullptr;
         this->n = other.n;
         other.n = 0; // n may represents array size
         return *this;
     }
private:
    double* m_d;
    unsigned int n;
};