简体   繁体   English

如何定义移动构造函数?

[英]How to define a move constructor?

I am trying some new C++11 features on visual studio 11, started with the move constructor.我正在 Visual Studio 11 上尝试一些新的 C++11 功能,从移动构造函数开始。 I wrote a simple class called "MyClass" containing a move constructor:我编写了一个名为“MyClass”的简单类,其中包含一个移动构造函数:

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

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

    ~MyClass() 
    {
        delete mpiSize;
    }

private:
    int *mpiSize;
    int miSize2;

};

I got there questions here:我在这里有问题:

  1. I assumed that the compiler would generate a move constructor for MyClass if I don't implement one - but it doesn't seems so?我假设如果我不实现一个,编译器会为 MyClass 生成一个移动构造函数 - 但它似乎不是这样?
  2. Is the implementation of the move constructor correct for MyClass?移动构造函数的实现对 MyClass 是否正确?
  3. Is there a better way to implement the move constructor for MyClass?有没有更好的方法来实现 MyClass 的移动构造函数?
  1. MSVC++ implemented move constructors before the final version of the standard was out. MSVC++ 在标准的最终版本发布之前实现了移动构造函数。 In the version of the standard MSVC++'s implementation was based on, the rules for generating a default move constructor were ridiculously more strict than they are in the final version of the standard.在基于 MSVC++ 实现的标准版本中,生成默认移动构造函数的规则比标准的最终版本严格得多。 See here: Why is this code trying to call the copy constructor?请参阅此处: 为什么此代码试图调用复制构造函数? (specifically this answer and the comments on it) for more info on that. (特别是这个答案及其评论)以获取更多信息。 This has not been and will not be fixed in Visual Studio 11, for some unknown stupid reason because they had other priorities.这在 Visual Studio 11 中没有也不会修复, 因为一些未知的愚蠢原因, 因为他们有其他优先事项。

  2. No, you need to call std::move on the members of rcOther , and you initialise members with the corresponding members from the dying object (you misnamed miSize ):不,您需要对rcOther的成员调用std::move ,并使用垂死对象中的相应成员初始化成员(您错误命名为miSize ):

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

    It doesn't make a difference for built in types like int and int* , but it definitely makes a difference for user-defined types.它对像intint*这样的内置类型没有影响,但对用户定义的类型肯定有影响。

    • The reason for this is that std::move just returns the argument casted into a T&& , an rvalue-reference, so that the correct constructor (the move constructor, T(T&&) ) is called for each of the sub-objects.这样做的原因是std::move只返回转换为T&&的参数,一个右值引用,以便为每个子对象调用正确的构造函数(移动构造函数, T(T&&) )。 If you don't use std::move on the members of the dying object, they will be treated like T& , and the copy constructor of your subobjects ( T(T&) ) will be called instead of the move constructor.如果您不在垂死对象的成员上使用std::move ,它们将被视为T& ,并且您的子对象( T(T&) )的复制构造函数将被调用而不是移动构造函数。 That is very bad and thwarts almost the entire purpose of you having written a move constructor.这是非常糟糕的,并且几乎阻碍了您编写移动构造函数的全部目的。


3. You are doing some unnecessary things, like setting the integer to 0. You only need to set the pointer to 0 so that delete ing it won't delete the resource of the new object you created. 3.你在做一些不必要的事情,比如将整数设置为0。你只需要将指针设置为0,这样delete ing它就不会删除你创建的新对象的资源。

Also, if this is not a didactic exercise, you may want to consider using std::unique_ptr instead of managing the lifetime of your own object.此外,如果这不是教学练习,您可能需要考虑使用std::unique_ptr而不是管理您自己的对象的生命周期。 This way you don't even have to write a destructor for your class.这样你甚至不必为你的类编写析构函数。 Note that if you do that, using std::move to initialise the member from the dying member in the move constructor would be mandatory.请注意,如果您这样做,则必须使用std::move从移动构造函数中的垂死成员中初始化该成员。


  • Konrad Rudolph in his answer has caught the fact that your class manages a non-automatic resource but does not follow the Rule of Five Three, Four, or Five .康拉德鲁道夫在他的回答中发现了这样一个事实,即您的班级管理非自动资源但不遵循 三、四或五规则 See his answer for more details on this.有关更多详细信息,请参阅他的回答。

Why doesn't the compiler auto-generate a move constructor?为什么编译器不自动生成移动构造函数?

The compiler does generate a move constructor if you don't do so – after a fashion.如果您不这样做,编译器确实会生成一个移动构造函数 - 在一种方式之后。 However, the compiler cannot second-guess your motives so it doesn't know what the pointer in your class does.但是,编译器无法猜测您的动机,因此它不知道您的类中的指针是做什么的。 In particular, it doesn't know that the pointer confers ownership of memory and needs to be nulled out.特别是,它不知道指针授予内存所有权,需要清空。

Is the implementation of the move constructor correct?移动构造函数的实现是否正确?

The move constructor is correct移动构造函数是正确的1 but the rest of the class isn't, you are violating the rule of three : your class needs an appropriate copy constructor and copy assignment operator. 1但类的其余部分不是,您违反了三规则:您的类需要适当的复制构造函数和复制赋值运算符。

Is there a better way to implement the move constructor?有没有更好的方法来实现移动构造函数?

A better way to write the move constructor looks as follows:编写移动构造函数的更好方法如下所示:

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

Two comments:两条评论:

  • Why did you not directly copy the members, instead dereferencing rcOther.mpiSize ?为什么不直接复制成员,而是取消引用rcOther.mpiSize While this isn't wrong, it also makes no sense and is misleading.虽然这没有错,但它也没有任何意义并且具有误导性。
  • You do not need to zero out the integer, and since it's unnecessary it shouldn't be done: The only modification your move constructor should perform on the moved-from object is to relinquish ownership of its resources so that it can be destroyed without causing resources to be doubly deleted.您不需要将整数清零,并且因为它是不必要的,所以不应该这样做:您的移动构造函数应该对移动的对象执行的唯一修改是放弃其资源的所有权,以便它可以被销毁而不会导致资源被双重删除。

But an even better way is to rely on pre-existing facilities .但更好的方法是依靠预先存在的设施 In this case, you want to model memory ownership.在这种情况下,您希望对内存所有权进行建模。 A naked pointer does this poorly, you should use a std::unique_ptr instead.裸指针在这方面做得很差,您应该改用std::unique_ptr This way, you don't need to implement either destructor nor move constructor since the the auto-generated methods do the right thing.这样,您不需要实现析构函数或移动构造函数,因为自动生成的方法做正确的事情。


1 Caveat : See Seth's answer for a better explanation that mentions std::move (which is a no-op in this particular case, however). 1警告:请参阅 Seth 的回答以获得更好的解释,其中提到了std::move (然而,在这种特殊情况下这是一个无操作)。

Since C++14 you can take advantage of the std::exchange() convenience function template for defining the move constructor.从 C++14 开始,您可以利用std::exchange()便利函数模板来定义移动构造函数。 This may result in a more concise move constructor definition:这可能会导致更简洁的移动构造函数定义:

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

std::exchange(other.mpiSize, nullptr) replaces the value of other.mpiSize with nullptr but returns the value other.mpiSize had prior to the replacement. std::exchange(other.mpiSize, nullptr)取代的价值other.mpiSizenullptr ,但返回值other.mpiSize之前更换了。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM