如何定义移动构造函数?

时间:2012-02-26 20:36:58

标签: c++ c++11

我在visual studio 11上尝试了一些新的C ++ 11功能,从移动构造函数开始。我写了一个名为“MyClass”的简单类,其中包含一个移动构造函数:

class MyClass
{
public:
    explicit MyClass( int aiCount ) 
        : mpiSize( new int( aiCount ) ),
          miSize2( aiCount)
    {
    }

    MyClass( MyClass&& rcOther )
        : mpiSize( rcOther.mpiSize )
        , miSize( *rcOther.mpiSize )
    {
       rcOther.mpiSize = 0;
       rcOther.miSize = 0;
    }

    ~MyClass() 
    {
        delete mpiSize;
    }

private:
    int *mpiSize;
    int miSize2;

};

我在这里有问题:

  1. 我假设编译器会为MyClass生成一个移动构造函数,如果我没有实现它 - 但它似乎不是这样吗?
  2. 移动构造函数的实现是否适用于MyClass?
  3. 有没有更好的方法来实现MyClass的移动构造函数?

3 个答案:

答案 0 :(得分:25)

  1. MSVC ++在标准的最终版本发布之前实现了移动构造函数。在标准MSVC ++的实现版本的基础上,生成默认移动构造函数的规则比标准的最终版本更严格。有关详细信息,请参见此处:Why is this code trying to call the copy constructor?(具体为this answer及其评论)。这已经并且不会在Visual Studio 11中修复,出于某种未知的愚蠢原因,因为它们还有其他优先事项。

  2. 不,您需要在std::move的成员上调用rcOther,并使用垂死对象(您错误命名为miSize)中的相应成员初始化成员:< / p>

    MyClass( MyClass&& rcOther )
        : mpiSize( std::move(rcOther.mpiSize) )
        , miSize2( std::move(rcOther.miSize2) )
    {
       rcOther.mpiSize = 0;
    }
    

    它对intint*等内置类型没有什么影响,但肯定会对用户定义的类型产生影响。

    • 这样做的原因是std::move只返回转换为T&& rvalue-reference的参数,以便正确的构造函数(移动构造函数,T(T&&) )为每个子对象调用。如果您不对临终对象的成员使用std::move,则会将它们视为T&,并且将调用子对象(T(T&))的复制构造函数而不是移动构造函数。这是非常糟糕的,几乎是你编写移动构造函数的全部目的。

  3. 3.你正在做一些不必要的事情,比如将整数设置为0.你只需要将指针设置为0,这样delete它就不会删除你创建的新对象的资源。

    此外,如果这不是一个教学练习,您可能需要考虑使用std::unique_ptr而不是管理自己对象的生命周期。这样你甚至不必为你的类编写析构函数。请注意,如果您这样做,则必须使用std::move从移动构造函数中的垂死成员初始化成员。


    • Konrad Rudolph在他的回答中发现了一个事实,即你的班级管理一个非自动资源,但不遵循 Five 三,四或五的规则。有关详细信息,请参阅他的回答。

答案 1 :(得分:12)

为什么编译器不自动生成移动构造函数?

如果你不这样做,编译器生成一个移动构造函数 - 一个时尚之后。但是,编译器无法猜测您的动机,因此它不知道您的类中的指针是什么。特别是,它不知道指针赋予了内存的所有权,需要被淘汰。

移动构造函数的实现是否正确?

移动构造函数是正确的 1 但是类的其余部分没有,你违反了rule of three:你的类需要一个合适的复制构造函数和复制赋值运算符。

有没有更好的方法来实现移动构造函数?

编写移动构造函数的更好方法如下:

MyClass(MyClass&& rcOther)
    : mpiSize(std::move(rcOther.mpiSize))
    , miSize2(std::move(rcOther.miSize2))
{
    rcOther.mpiSize = 0;
}

两条评论:

  • 为什么不直接复制成员,而是取消引用rcOther.mpiSize?虽然这没有错,但它也毫无意义并且具有误导性。
  • 你不需要将整数归零,并且因为它是不必要的,所以不应该这样做:你的移动构造函数应该对移动的对象执行的唯一修改是放弃其资源的所有权,以便它可以是在不造成资源双重删除的情况下销毁。

但更好的方法是依靠已有的设施。在这种情况下,您需要为内存所有权建模。裸指针做得不好,你应该使用std::unique_ptr代替。这样,您不需要实现析构函数也不需要移动构造函数,因为自动生成的方法是正确的。


1 警告:请参阅Seth的答案,以获得更好的解释,提及std::move(在这种特殊情况下,这是一个无操作)。 / p>

答案 2 :(得分:1)

从C ++ 14开始,您可以利用std::exchange()便捷函数模板来定义move构造函数。这可能会导致更简洁的move构造函数定义:

MyClass(MyClass&& other) noexcept:
   mpiSize(std::exchange(other.mpiSize, nullptr)),
   miSize2(std::exchange(other.miSize2, 0))
{}

std::exchange(other.mpiSize, nullptr)other.mpiSize替换nullptr的值,但返回替换前的值other.mpiSize