最佳C ++移动构造函数实现实践

时间:2016-04-04 14:26:50

标签: c++ c++11 move move-constructor

我试图理解move-constructor的实现。 我们都知道如果我们需要管理C ++类中的资源,我们需要实现五阶规则(C ++编程)。

Microsoft给我们举了一个例子:https://msdn.microsoft.com/en-us/library/dd293665.aspx

这是更好的一个,它使用copy-swap来避免代码重复: Dynamically allocating an array of objects

     // C++11
     A(A&& src) noexcept
         : mSize(0)
         , mArray(NULL)
     {
         // Can we write src.swap(*this);
         // or (*this).swap(src);
         (*this) = std::move(src); // Implements in terms of assignment
     }

在move-constructor中,直接:

         // Can we write src.swap(*this);
         // or (*this).swap(src);

因为我认为(*this) = std::move(src)稍微复杂一些。因为如果我们无意中写为(*this) = src,它会调用普通赋值运算符而不是move-assignment-operator。

除了这个问题,在微软的例子中,他们编写了这样的代码:在move-assignment-operator中,我们是否需要检查自我分配?有可能发生吗?

// Move assignment operator.
MemoryBlock& operator=(MemoryBlock&& other)
{
   std::cout << "In operator=(MemoryBlock&&). length = " 
             << other._length << "." << std::endl;

   if (this != &other)
   {
      // Free the existing resource.
      delete[] _data;

      // Copy the data pointer and its length from the 
      // source object.
      _data = other._data;
      _length = other._length;

      // Release the data pointer from the source object so that
      // the destructor does not free the memory multiple times.
      other._data = nullptr;
      other._length = 0;
   }
   return *this;
}

2 个答案:

答案 0 :(得分:6)

一种方法是实现默认构造函数,复制构造函数和swap函数。

然后实现移动构造函数,使用前三个来复制和移动赋值运算符。

E.g:

struct X
{
    X();
    X(X const&);
    void swap(X&) noexcept;

    X(X&& b)
        : X() // delegate to the default constructor
    {
        b.swap(*this);
    }

    // Note that this operator implements both copy and move assignments.
    // It accepts its argument by value, which invokes the appropriate (copy or move) constructor.
    X& operator=(X b) {
        b.swap(*this);
        return *this;
    }
};

如果您在C ++ 98中使用过这个习惯用法,那么一旦添加了移动构造函数,就可以获得移动赋值而无需编写任何代码。

In some cases this idiom may be not the most efficient。因为复制操作符总是首先构造一个临时的,然后与它交换。通过手动编码赋值运算符,可以获得更好的性能。如有疑问,请检查优化的装配输出并使用分析器。

答案 1 :(得分:0)

我也在寻找Internet,以找到实现move构造函数和move分配的最佳方法。方法很少,但都不是完美的。

以下是我到目前为止的发现。

以下是我正在使用的Test类作为示例:

class Test {
private:
    void*       handle_ = nullptr;
    std::string name_;

public:
    ~Test(); 

    Test(std::string name);
    Test(Test&& other) noexcept;

    Test& operator=(Test&& other) noexcept;
};

方法1:直截了当

    Test(Test&& other) noexcept
        : handle_(std::exchange(other.handle_, nullptr))
        , name_(std::move(other.name_))
    {
    }

    Test& operator=(Test&& other) noexcept
    {
        if(handle_) close_handle(handle_);
        
        handle_ = std::exchange(other.handle_, nullptr);
        name_ = std::move(other.name_);

        return *this;
    }

专业人士

  • 最佳表现

缺点

  • 移动构造函数和移动分配中的代码重复

方法2:破坏+构建

    Test& operator=(Test&& other) noexcept
    {
        if(&other == this) return *this;

        this->~Test();
        new (this) Test(std::move(other));

        return *this;
    }

专业人士

  • 没有代码重复
  • 如果没有虚拟功能,性能很好

缺点

  • 虚拟方法表(VMT)初始化两次(如果类具有虚函数)
  • 如果operator=在基类上运行,则将分配基类的VMT
  • 再设立一个分支机构,以确保我们不会自我毁灭

方法3:“复制”交换

    Test& operator=(Test&& other) noexcept
    {
        Test (std::move(other)).swap(*this);
        return *this;
    }

或复制和移动运算符2合1:

    Test& operator=(Test other) noexcept
    {
        std::swap(other, *this);
        return *this;
    }

专业人士

  • 少1个分支
  • 没有代码重复

缺点

  • 已创建额外对象
  • 交换数据成员时创建的其他较小对象

方法4:通过移动分配移动构造函数

这就是您@Dongguo在MSDN上找到的内容

    Test(Test&& other) noexcept
    {
        *this = std::move(other);
    }

    Test& operator=(Test&& other) noexcept
    {
        if(handle_) close_handle(handle_);
        
        handle_ = std::exchange(other.handle_, nullptr);
        name_ = std::move(other.name_);

        return *this;
    }

专业人士

  • 没有代码重复

缺点

  • 不适用于默认不可构造的类

链接

更多答案