如何定义移动构造函数?

Mar*_*ndl 19 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;

};
Run Code Online (Sandbox Code Playgroud)

我在这里有问题:

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

Set*_*gie 30

  1. MSVC++在标准的最终版本发布之前实现了移动构造函数.在标准MSVC++的实现版本的基础上,生成默认移动构造函数的规则比标准的最终版本更严格.看到这里:为什么这段代码试图调用复制构造函数?(具体来说,这个答案及其评论)有关这方面的更多信息.由于它们有其他优先级,因此一些未知的愚蠢原因在Visual Studio 11中没有也不会修复.

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

    MyClass( MyClass&& rcOther )
        : mpiSize( std::move(rcOther.mpiSize) )
        , miSize2( std::move(rcOther.miSize2) )
    {
       rcOther.mpiSize = 0;
    }
    
    Run Code Online (Sandbox Code Playgroud)

    对于像int和的内置类型而言int*,它没有什么区别,但它肯定会对用户定义的类型产生影响.

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


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

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


  • 康拉德·鲁道夫在他的回答已经引起了你的类管理非自动的资源,但不遵循事实的规则三,四,五.有关详细信息,请参阅他的回答.

  • 至于"未知,愚蠢的原因",Herb Sutter在[他在Going Native 2012上的演讲]中介绍了微软C++团队的发布时间表(http://channel9.msdn.com/Events/GoingNative/GoingNative-2012/C- 11-VC-11 - 和 - 超越).虽然你可能不同意他们的优先事项,但原因似乎既不愚蠢也不为人所知(虽然它可能会在微软的网站上变得更加明显). (2认同)

Kon*_*lph 13

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

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

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

移动构造函数是正确的1但是其他类没有,你违反了三条规则:你的类需要一个合适的拷贝构造函数和拷贝赋值运算符.

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

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

MyClass(MyClass&& rcOther)
    : mpiSize(std::move(rcOther.mpiSize))
    , miSize2(std::move(rcOther.miSize2))
{
    rcOther.mpiSize = 0;
}
Run Code Online (Sandbox Code Playgroud)

两条评论:

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

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


1 警告:请参阅塞思的回答,以便提及更好的解释std::move(但在这种特殊情况下,这是一个无操作).

  • @Seth [这是一个有争议的问题](http://stackoverflow.com/q/4782757/1968).*在正确性方面*,三个规则仍然是三个规则.我同意作为设计指南,我们应该始终使用五条规则. (2认同)

眠りネ*_*ネロク 5

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

MyClass(MyClass&& other) noexcept:
   mpiSize(std::exchange(other.mpiSize, nullptr)),
   miSize2(std::exchange(other.miSize2, 0))
{}
Run Code Online (Sandbox Code Playgroud)

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