默认移动构造函数vs默认复制构造函数vs默认赋值操作符

时间:2021-11-22 21:42:04

Why does C++ compiler have more restriction on automatically generated move constructors than on automatically generated copy constructor or assignment operator ?

为什么c++编译器对自动生成的移动构造函数比对自动生成的复制构造函数或赋值操作符有更多的限制?

Automatically generated move constructors are generated only if user has defined nothing (i.e: constructor, copy, assignment, destructor..)

只有当用户没有定义(i)时,才会自动生成移动构造函数。e:构造函数、复制、赋值、析构函数。

Copy constructor or assignment operator are generated only if user has not defined respectively copy constructor or assignment operator.

复制构造函数或赋值运算符仅在用户未分别定义复制构造函数或赋值运算符时才生成。

I wonder why the difference.

我想知道为什么会有不同。

3 个解决方案

#1


13  

I believe backwards compatibility plays a big part here. If the user defines any of the "Rule of three" functions (copy ctor, copy assignment op, dtor), it can be assumed the class does some internal resource management. Implicitly defining a move constructor could suddenly make the class invalid when compiled under C++11.

我认为向后兼容性在这里扮演了重要的角色。如果用户定义了“三个规则”中的任何一个函数(copy ctor、copy assignment op、dtor),则可以假定该类执行一些内部资源管理。在c++ 11下编译时,隐式定义move构造函数可能会突然使类无效。

Consider this example:

考虑一下这个例子:

class Res
{
  int *data;

public:
  Res() : data(new int) {}

  Res(const Res &arg) : data(new int(*arg.data)) {}

  ~Res() { delete data; }
};

Now if a default move constructor was generated for this class, its invocation would lead to a double deletion of data.

现在,如果为该类生成一个默认的move构造函数,那么它的调用将导致数据的双重删除。

As for the move assignment operator preventing default move constructor definitions: if the move assignment operator does something other than default one, it would most likely be wrong to use the default move constructor. That's just the "Rule of three"/"Rule of five" in effect.

至于移动赋值操作符防止默认移动构造函数定义:如果移动赋值操作符执行的不是默认赋值操作符,那么使用默认的移动构造函数很可能是错误的。这实际上就是“三原则”/“五原则”。

#2


9  

As far as I know, this is because of downward compatibility. Consider classes written in C++ (before C++11) and what would happen if C++11 would start to automatically generate move-ctors in parallel to existing copy-ctors or generally any other ctor. It would easily break existing code, by-passing the copy-ctor the author of that class wrote. Hence, the rules for generating a move-ctor where crafted to only apply to "safe" cases.

据我所知,这是因为向下兼容。考虑使用c++编写的类(在c++ 11之前),以及如果c++ 11开始自动地生成与现有的copy-ctors或任何其他的ctor并行的动作-ctor会发生什么。它很容易破坏现有的代码,绕过该类作者编写的copy-ctor。因此,生成一个移动转换器的规则只适用于“安全”的情况。

Here's the article from Dave Abrahams about why implicit move must go, which eventually led to the current rules of C++11.

这是戴夫·亚伯拉罕斯关于为什么必须要进行隐式移动的文章,它最终导致了当前c++ 11的规则。

And this is an example how it would fail:

这是一个失败的例子

// NOTE: This example assumes an implicitly generated move-ctor

class X
{
private:    
    std::vector<int> v;

public:
    // invariant: v.size() == 5
    X() : v(5) {}

    ~X()
    {
        std::cout << v[0] << std::endl;
    }
};

int main()
{
    std::vector<X> y;

    // and here is where it would fail:
    // X() is an rvalue: copied in C++03, moved in C++0x
    // the classes' invariant breaks and the dtor will illegally access v[0].
    y.push_back(X());
}

#3


9  

When C++ was created, it was decided that default constructor, copy-constructor, assignment-operator and destructor would be generated automatically (unless provided). Why ? Because C++ compilers should be able to compile (most) C code with identical semantics, and that's how struct work in C.

在创建c++时,决定自动生成默认构造函数、复制构造函数、赋值操作符和析构函数(除非提供)。为什么?因为c++编译器应该能够编译(大多数)具有相同语义的C代码,这就是结构在C中的工作方式。

However, it was later noticed that whenever a user writes a custom destructor, she probably needs to write a custom copy-constructor/assignment-operator too; this is known as the Rule of Big Three. With hindsight, we can see that it could have been specified that the generated copy-constructor/assignment-operator/destructor would have only been generated if none of the 3 were user-provided, and it would have helped catch a lot of bugs... and still retain backward compatibility with C.

然而,后来人们注意到,每当用户编写自定义析构函数时,她可能也需要编写自定义复制构造函数/赋值操作符;这就是三巨头的规则。事后来看,我们可以看到,我们可以指定,生成的复制构造函数/赋值操作符/析构函数/析构函数只有在3个都不是用户提供的情况下才会生成,这将有助于捕获大量错误……并且仍然保留与C的向后兼容性。

Therefore, as C++11 came around, it was decided that this time things would be done right: the new move-constructor and move-assignment-operator would only be generated automatically if it was clear that the user was not doing anything "special" with the class. Anything "special" being defined as redefining move/copy/destruction behavior.

因此,随着c++ 11的出现,我们决定这次要做的事情是正确的:新的移动构造函数和移动赋值操作符只有在用户明显没有对类做任何“特殊”操作时才会自动生成。任何“特殊”被定义为重新定义移动/复制/破坏行为。

To help with the case were people would be doing something special but still wanted "automatically generated" special methods, the = default sugar-coating was added as well.

为了解决这个问题,人们会做一些特别的事情,但仍然想要“自动生成”的特殊方法,也添加了默认的糖衣。

Unfortunately, for backward compatibility reasons, the C++ committee could not go back in time and change the rules of automatic generation for copy; I wish they had deprecated it to pave the way for the next version of the Standard, but I doubt they will. it is however deprecated (see §12.8/7 for the copy constructor for example, courtesy of @Nevin).

不幸的是,由于向后兼容的原因,c++委员会无法及时返回并更改自动生成规则进行复制;我希望他们已经弃用它为下一个版本的标准铺平道路,但我怀疑他们会这么做。然而弃用(见§12.8/7的拷贝构造函数为例,由@Nevin)。

#1


13  

I believe backwards compatibility plays a big part here. If the user defines any of the "Rule of three" functions (copy ctor, copy assignment op, dtor), it can be assumed the class does some internal resource management. Implicitly defining a move constructor could suddenly make the class invalid when compiled under C++11.

我认为向后兼容性在这里扮演了重要的角色。如果用户定义了“三个规则”中的任何一个函数(copy ctor、copy assignment op、dtor),则可以假定该类执行一些内部资源管理。在c++ 11下编译时,隐式定义move构造函数可能会突然使类无效。

Consider this example:

考虑一下这个例子:

class Res
{
  int *data;

public:
  Res() : data(new int) {}

  Res(const Res &arg) : data(new int(*arg.data)) {}

  ~Res() { delete data; }
};

Now if a default move constructor was generated for this class, its invocation would lead to a double deletion of data.

现在,如果为该类生成一个默认的move构造函数,那么它的调用将导致数据的双重删除。

As for the move assignment operator preventing default move constructor definitions: if the move assignment operator does something other than default one, it would most likely be wrong to use the default move constructor. That's just the "Rule of three"/"Rule of five" in effect.

至于移动赋值操作符防止默认移动构造函数定义:如果移动赋值操作符执行的不是默认赋值操作符,那么使用默认的移动构造函数很可能是错误的。这实际上就是“三原则”/“五原则”。

#2


9  

As far as I know, this is because of downward compatibility. Consider classes written in C++ (before C++11) and what would happen if C++11 would start to automatically generate move-ctors in parallel to existing copy-ctors or generally any other ctor. It would easily break existing code, by-passing the copy-ctor the author of that class wrote. Hence, the rules for generating a move-ctor where crafted to only apply to "safe" cases.

据我所知,这是因为向下兼容。考虑使用c++编写的类(在c++ 11之前),以及如果c++ 11开始自动地生成与现有的copy-ctors或任何其他的ctor并行的动作-ctor会发生什么。它很容易破坏现有的代码,绕过该类作者编写的copy-ctor。因此,生成一个移动转换器的规则只适用于“安全”的情况。

Here's the article from Dave Abrahams about why implicit move must go, which eventually led to the current rules of C++11.

这是戴夫·亚伯拉罕斯关于为什么必须要进行隐式移动的文章,它最终导致了当前c++ 11的规则。

And this is an example how it would fail:

这是一个失败的例子

// NOTE: This example assumes an implicitly generated move-ctor

class X
{
private:    
    std::vector<int> v;

public:
    // invariant: v.size() == 5
    X() : v(5) {}

    ~X()
    {
        std::cout << v[0] << std::endl;
    }
};

int main()
{
    std::vector<X> y;

    // and here is where it would fail:
    // X() is an rvalue: copied in C++03, moved in C++0x
    // the classes' invariant breaks and the dtor will illegally access v[0].
    y.push_back(X());
}

#3


9  

When C++ was created, it was decided that default constructor, copy-constructor, assignment-operator and destructor would be generated automatically (unless provided). Why ? Because C++ compilers should be able to compile (most) C code with identical semantics, and that's how struct work in C.

在创建c++时,决定自动生成默认构造函数、复制构造函数、赋值操作符和析构函数(除非提供)。为什么?因为c++编译器应该能够编译(大多数)具有相同语义的C代码,这就是结构在C中的工作方式。

However, it was later noticed that whenever a user writes a custom destructor, she probably needs to write a custom copy-constructor/assignment-operator too; this is known as the Rule of Big Three. With hindsight, we can see that it could have been specified that the generated copy-constructor/assignment-operator/destructor would have only been generated if none of the 3 were user-provided, and it would have helped catch a lot of bugs... and still retain backward compatibility with C.

然而,后来人们注意到,每当用户编写自定义析构函数时,她可能也需要编写自定义复制构造函数/赋值操作符;这就是三巨头的规则。事后来看,我们可以看到,我们可以指定,生成的复制构造函数/赋值操作符/析构函数/析构函数只有在3个都不是用户提供的情况下才会生成,这将有助于捕获大量错误……并且仍然保留与C的向后兼容性。

Therefore, as C++11 came around, it was decided that this time things would be done right: the new move-constructor and move-assignment-operator would only be generated automatically if it was clear that the user was not doing anything "special" with the class. Anything "special" being defined as redefining move/copy/destruction behavior.

因此,随着c++ 11的出现,我们决定这次要做的事情是正确的:新的移动构造函数和移动赋值操作符只有在用户明显没有对类做任何“特殊”操作时才会自动生成。任何“特殊”被定义为重新定义移动/复制/破坏行为。

To help with the case were people would be doing something special but still wanted "automatically generated" special methods, the = default sugar-coating was added as well.

为了解决这个问题,人们会做一些特别的事情,但仍然想要“自动生成”的特殊方法,也添加了默认的糖衣。

Unfortunately, for backward compatibility reasons, the C++ committee could not go back in time and change the rules of automatic generation for copy; I wish they had deprecated it to pave the way for the next version of the Standard, but I doubt they will. it is however deprecated (see §12.8/7 for the copy constructor for example, courtesy of @Nevin).

不幸的是,由于向后兼容的原因,c++委员会无法及时返回并更改自动生成规则进行复制;我希望他们已经弃用它为下一个版本的标准铺平道路,但我怀疑他们会这么做。然而弃用(见§12.8/7的拷贝构造函数为例,由@Nevin)。