C++ 通过调用Move赋值运算符实现Move构造函数

C++ 通过调用Move赋值运算符实现Move构造函数,c++,c++11,move-semantics,move-constructor,C++,C++11,Move Semantics,Move Constructor,MSDN文章有以下建议 如果为类提供移动构造函数和移动赋值运算符,则可以通过编写 移动构造函数调用移动赋值运算符。这个 下面的示例显示了move构造函数的修订版本 调用移动分配操作符: 通过双重初始化MemoryBlock的值,这段代码是否效率低下,或者编译器是否能够优化掉额外的初始化?我是否应该始终通过调用move赋值操作符来编写move构造函数?我认为您不会注意到显著的性能差异。我认为从移动构造函数中使用移动赋值操作符是不错的做法。< /P> 但是,我宁愿使用std::forward而不是s

MSDN文章有以下建议

如果为类提供移动构造函数和移动赋值运算符,则可以通过编写 移动构造函数调用移动赋值运算符。这个 下面的示例显示了move构造函数的修订版本 调用移动分配操作符:


通过双重初始化
MemoryBlock
的值,这段代码是否效率低下,或者编译器是否能够优化掉额外的初始化?我是否应该始终通过调用move赋值操作符来编写move构造函数?

我认为您不会注意到显著的性能差异。我认为从移动构造函数中使用移动赋值操作符是不错的做法。< /P> 但是,我宁愿使用std::forward而不是std::move,因为它更符合逻辑:

*this = std::forward<MemoryBlock>(other);
*this=std::forward(其他);

我不会这样做。移动成员首先存在的原因是性能。为你的行动做这件事就像花巨资买一辆超级汽车,然后试图通过购买普通汽油来省钱

如果您想减少编写的代码量,就不要编写移动成员。在移动上下文中,您的类可以很好地复制

如果您希望您的代码具有高性能,那么可以调整移动构造函数和移动分配,使其尽可能快。好的移动成员会非常快,你应该通过计算负载、存储和分支来估计他们的速度。如果你可以用4个加载/存储而不是8个来写东西,那就去做吧!如果你可以写一些没有分支而不是1的东西,那就去做吧

当你(或你的客户)将你的类放入一个
std::vector
中时,你的类型会产生大量的移动。即使你的移动速度在8次加载/存储时是闪电般的快,如果你能在4次或6次加载/存储时使速度提高两倍,甚至快50%,那么这是值得花费的时间

就我个人而言,我讨厌看到等待的游标,我愿意多花5分钟来编写我的代码,并且知道这是尽可能快的


如果您仍然不相信这是值得的,请用两种方法编写它,然后在完全优化时检查生成的程序集。谁知道呢,您的编译器可能足够聪明,可以为您优化掉额外的负载和存储。但此时您已经投入了比刚编写优化移动构造函数更多的时间。

这取决于移动赋值操作符的操作。如果您查看链接到的文章中的内容,您会看到部分内容:

  // Free the existing resource.
  delete[] _data;

因此,在此上下文中,如果您从移动构造函数调用移动赋值运算符,而不首先初始化
\u data
,那么您将尝试删除未初始化的指针。因此,在本例中,无论是否低效,初始化值实际上是至关重要的。

MyC++11版本的
MemoryBlock

#include <algorithm>
#include <vector>
// #include <stdio.h>

class MemoryBlock
{
 public:
  explicit MemoryBlock(size_t length)
    : length_(length),
      data_(new int[length])
  {
    // printf("allocating %zd\n", length);
  }

  ~MemoryBlock() noexcept
  {
    delete[] data_;
  }

  // copy constructor
  MemoryBlock(const MemoryBlock& rhs)
    : MemoryBlock(rhs.length_) // delegating to another ctor
  {
    std::copy(rhs.data_, rhs.data_ + length_, data_);
  }

  // move constructor
  MemoryBlock(MemoryBlock&& rhs) noexcept
    : length_(rhs.length_),
      data_(rhs.data_)
  {
    rhs.length_ = 0;
    rhs.data_ = nullptr;
  }

  // unifying assignment operator.
  // move assignment is not needed.
  MemoryBlock& operator=(MemoryBlock rhs) // yes, pass-by-value
  {
    swap(rhs);
    return *this;
  }

  size_t Length() const
  {
    return length_;
  }

  void swap(MemoryBlock& rhs)
  {
    std::swap(length_, rhs.length_);
    std::swap(data_, rhs.data_);
  }

 private:
  size_t length_;  // note, the prefix underscore is reserved.
  int*   data_;
};

int main()
{
   std::vector<MemoryBlock> v;
   // v.reserve(10);
   v.push_back(MemoryBlock(25));
   v.push_back(MemoryBlock(75));

   v.insert(v.begin() + 1, MemoryBlock(50));
}
#包括
#包括
//#包括
类内存块
{
公众:
显式内存块(大小和长度)
:长度(长度),
数据(新整数[长度])
{
//printf(“分配%zd\n”,长度);
}
~MemoryBlock()无例外
{
删除[]数据;
}
//复制构造函数
内存块(常量内存块和rhs)
:MemoryBlock(rhs.length)//委托给另一个ctor
{
标准::副本(rhs.data,rhs.data+长度,数据);
}
//移动构造函数
MemoryBlock(MemoryBlock&&rhs)无例外
:长度(右侧长度),
数据(右侧数据)
{
rhs.长度u0;
rhs.data_u2;=空PTR;
}
//统一赋值运算符。
//不需要移动分配。
MemoryBlock&operator=(MemoryBlock rhs)//是,按值传递
{
掉期(rhs);
归还*这个;
}
大小\u t长度()常数
{
返回长度;
}
无效交换(内存块和rhs)
{
标准::交换(长度,右侧长度);
标准::交换(数据交换,右数据交换);
}
私人:
size\u t length\u;//注意,前缀下划线是保留的。
int*数据;
};
int main()
{
std::向量v;
//五储备(10);
v、 向后推(内存块(25));
v、 向后推(内存块(75));
v、 插入(v.begin()+1,MemoryBlock(50));
}

如果使用正确的C++11编译器,
MemoryBlock::MemoryBlock(size\u t)
在测试程序中只应调用3次。

我只需消除成员初始化并编写

MemoryBlock(MemoryBlock&& other)
{
   *this = std::move(other);
}
除非移动分配抛出异常,否则这将始终有效,而且通常不会

这种样式的优点:

  • 您不必担心编译器是否会双重初始化成员,因为这在不同的环境中可能会有所不同
  • 你写的代码更少
  • 即使将来向类添加额外成员,也不需要更新它
  • 编译器通常可以内联移动分配,因此复制构造函数的开销将是最小的 我认为@Howard的帖子并没有完全回答这个问题。实际上,类通常不喜欢复制,许多类只是禁用复制构造函数和复制赋值。但是大多数类都可以移动,即使它们不可复制

    […]编译器能够优化掉额外的初始化吗

    几乎在所有情况下:是的

    我是否应该始终通过调用移动赋值操作符来编写移动构造函数

    是的,只需通过移动分配操作符来实现,除非您测量到它会导致次优性能。


    今天的优化器在优化代码方面做得非常出色。您的示例代码特别容易优化。首先:在几乎所有情况下,move构造函数都是内联的。如果您通过移动分配操作符实现它,那么该操作符也将内联

    让我们来看一些例子
    MemoryBlock(MemoryBlock&& other)
    {
       *this = std::move(other);
    }