即使不使用移动构造函数也是必需的。为什么?

时间:2022-11-26 10:43:43

Why?! Why C++ requires the class to be movable even if it's not used! For example:

为什么?!为什么C ++要求类可以移动,即使它没有被使用!例如:

#include <iostream>
using namespace std;

struct A {
    const int idx;
    //   It could not be compileld if I comment out the next line and uncomment
    // the line after the next but the moving constructor is NOT called anyway!
    A(A&& a) : idx(a.idx) { cout<<"Moving constructor with idx="<<idx<<endl; }
   //  A(A&& a) = delete;
    A(const int i) : idx(i) { cout<<"Constructor with idx="<<i<<endl; }
    ~A() { cout<<"Destructor with idx="<<idx<<endl; }
};

int main()
{
    A a[2] = { 0, 1 };
   return 0;
}

The output is (the move constructor is not called!):

输出是(不调用移动构造函数!):

Constructor with idx=0
Constructor with idx=1
Destructor with idx=1
Destructor with idx=0

idx = 0的构造方法idx = 1的构造方法idx = 1的析构函数idx = 0的析构函数

The code can not be compiled if moving constructor is deleted ('use of deleted function ‘A::A(A&&)’'. But if the constructor is not deleted it is not used! What a stupid restriction? Note: Why do I need it for? The practical meaning appears when I am trying to initialize an array of objects contains unique_ptr field. For example:

如果删除移动构造函数,则无法编译代码('使用已删除的函数'A :: A(A &&)''。但如果构造函数未被删除则不使用!多么愚蠢的限制?注意:为什么我需要它吗?当我尝试初始化包含unique_ptr字段的对象数组时,会出现实际意义。例如:

// The array of this class can not be initialized!
class B {
    unique_ptr<int> ref;
public:
    B(int* ptr) : ref(ptr)
        {  }
}
// The next class can not be even compiled!
class C {
    B arrayOfB[2] = { NULL, NULL };
}

And it gets even worse if you are trying to use a vector of unique_ptr's.

如果你试图使用unique_ptr的向量,它会变得更糟。

Okay. Thanks a lot to everybody. There is big confusion with all those copying/moving constructors and array initialization. Actually the question was about the situation when the compiler requires a copying consrtuctor, may use a moving construcor and uses none of them. So I'm going to create a new question a little bit later when I get a normal keyboard. I'll provide the link here.

好的。非常感谢大家。所有复制/移动构造函数和数组初始化都存在很大的混乱。实际上问题是关于编译器需要复制consrtuctor的情况,可能使用移动的construcor并且不使用它们。所以当我得到一个普通的键盘时,我将在稍后创建一个新问题。我会在这里提供链接。

P.S. I've created more specific and clear question - welcome to discuss it!

附:我已经创建了更具体和明确的问题 - 欢迎讨论它!

2 个解决方案

#1


6  

A a[2] = { 0, 1 };

This is aggregate initialization. §8.5.1 [dcl.init.aggr]/p2 of the standard provides that

这是聚合初始化。 §8.5.1[dcl.init.aggr] / p2标准规定了这一点

When an aggregate is initialized by an initializer list, as specified in 8.5.4, the elements of the initializer list are taken as initializers for the members of the aggregate, in increasing subscript or member order. Each member is copy-initialized from the corresponding initializer-clause.

当初始化程序列表初始化聚合时,如8.5.4中所述,初始化程序列表的元素将作为聚合成员的初始化程序,增加下标或成员顺序。每个成员都是从相应的initializer子句复制初始化的。

§8.5 [dcl.init]/p16, in turn, describes the semantics of copy initialization of class type objects:

反过来,§8.5[dcl.init] / p16描述了类类型对象的复制初始化的语义:

  • [...]
  • If the destination type is a (possibly cv-qualified) class type:
    • If the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, constructors are considered. The applicable constructors are enumerated (13.3.1.3), and the best one is chosen through overload resolution (13.3). The constructor so selected is called to initialize the object, with the initializer expression or expression-list as its argument(s). If no constructor applies, or the overload resolution is ambiguous, the initialization is ill-formed.
    • 如果初始化是直接初始化,或者它是复制初始化,其中源类型的cv-nonqualified版本与目标类相同,或者是派生类,则考虑构造函数。列举了适用的构造函数(13.3.1.3),并通过重载解析(13.3)选择最佳构造函数。调用所选的构造函数以初始化对象,初始化表达式或表达式列表作为其参数。如果没有构造函数适用,或者重载决策是不明确的,则初始化是错误的。

    • Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source type to the destination type or (when a conversion function is used) to a derived class thereof are enumerated as described in 13.3.1.4, and the best one is chosen through overload resolution (13.3). If the conversion cannot be done or is ambiguous, the initialization is ill-formed. The function selected is called with the initializer expression as its argument; if the function is a constructor, the call initializes a temporary of the cv-unqualified version of the destination type. The temporary is a prvalue. The result of the call (which is the temporary for the constructor case) is then used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization. In certain cases, an implementation is permitted to eliminate the copying inherent in this direct-initialization by constructing the intermediate result directly into the object being initialized; see 12.2, 12.8.
    • 否则(即,对于剩余的复制初始化情况),可以如13.3中所述枚举可以从源类型转换为目的地类型或(当使用转换函数时)到其派生类的用户定义的转换序列。 1.4,通过重载决策(13.3)选择最好的一个。如果转换不能完成或不明确,则初始化是错误的。选择的函数以初始化表达式作为参数调用;如果函数是构造函数,则调用初始化目标类型的cv-nonqualified版本的临时函数。临时是一个prvalue。然后,根据上述规则,调用的结果(对于构造函数的情况是临时的)用于直接初始化作为复制初始化目标的对象。在某些情况下,允许实现通过将中间结果直接构造到正在初始化的对象中来消除此直接初始化中固有的复制;见12.2,12.8。

  • 如果目标类型是(可能是cv限定的)类类型:如果初始化是直接初始化,或者它是复制初始化,其中源类型的cv-nonqualified版本与类相同,或者是派生类,目的地的类,构造函数被考虑。列举了适用的构造函数(13.3.1.3),并通过重载解析(13.3)选择最佳构造函数。调用所选的构造函数以初始化对象,初始化表达式或表达式列表作为其参数。如果没有构造函数适用,或者重载决策是不明确的,则初始化是错误的。否则(即,对于剩余的复制初始化情况),可以如13.3中所述枚举可以从源类型转换为目的地类型或(当使用转换函数时)到其派生类的用户定义的转换序列。 1.4,通过重载决策(13.3)选择最好的一个。如果转换不能完成或不明确,则初始化是错误的。选择的函数以初始化表达式作为参数调用;如果函数是构造函数,则调用初始化目标类型的cv-nonqualified版本的临时函数。临时是一个prvalue。然后,根据上述规则,调用的结果(对于构造函数的情况是临时的)用于直接初始化作为复制初始化目标的对象。在某些情况下,允许实现通过将中间结果直接构造到正在初始化的对象中来消除此直接初始化中固有的复制;见12.2,12.8。

Since 0 and 1 are ints, not As, the copy initialization here falls under the second clause. The compiler find a user-defined conversion from int to A in your A::A(int) constructor, calls it to construct a temporary of type A, then performs direct initialization using that temporary. This, in turn, means the compiler is required to perform overload resolution for a constructor taking a temporary of type A, which selects your deleted move constructor, which renders the program ill-formed.

由于0和1是整数,而不是As,因此复制初始化属于第二个子句。编译器在A :: A(int)构造函数中找到用户定义的从int到A的转换,调用它来构造类型A的临时值,然后使用该临时值执行直接初始化。反过来,这意味着编译器需要对构造函数执行重载解析,该构造函数采用类型A的临时值,它选择已删除的移动构造函数,这会导致程序格式错误。

#2


11  

A a[2] = { 0, 1 };

Conceptually, this creates two temporary A objects, A(0) and A(1), and moves or copies them to initialise the array a; so a move or copy constructor is required.

从概念上讲,这会创建两个临时A对象A(0)和A(1),并移动或复制它们以初始化数组a;所以需要移动或复制构造函数。

As an optimisation, the move or copy is allowed to be elided, which is why your program doesn't appear to use the move constructor. But there must still be a suitable constructor, even if its use is elided.

作为优化,允许移除或复制,这就是您的程序似乎不使用移动构造函数的原因。但是,即使它的使用被省略,仍然必须有一个合适的构造函数。

#1


6  

A a[2] = { 0, 1 };

This is aggregate initialization. §8.5.1 [dcl.init.aggr]/p2 of the standard provides that

这是聚合初始化。 §8.5.1[dcl.init.aggr] / p2标准规定了这一点

When an aggregate is initialized by an initializer list, as specified in 8.5.4, the elements of the initializer list are taken as initializers for the members of the aggregate, in increasing subscript or member order. Each member is copy-initialized from the corresponding initializer-clause.

当初始化程序列表初始化聚合时,如8.5.4中所述,初始化程序列表的元素将作为聚合成员的初始化程序,增加下标或成员顺序。每个成员都是从相应的initializer子句复制初始化的。

§8.5 [dcl.init]/p16, in turn, describes the semantics of copy initialization of class type objects:

反过来,§8.5[dcl.init] / p16描述了类类型对象的复制初始化的语义:

  • [...]
  • If the destination type is a (possibly cv-qualified) class type:
    • If the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, constructors are considered. The applicable constructors are enumerated (13.3.1.3), and the best one is chosen through overload resolution (13.3). The constructor so selected is called to initialize the object, with the initializer expression or expression-list as its argument(s). If no constructor applies, or the overload resolution is ambiguous, the initialization is ill-formed.
    • 如果初始化是直接初始化,或者它是复制初始化,其中源类型的cv-nonqualified版本与目标类相同,或者是派生类,则考虑构造函数。列举了适用的构造函数(13.3.1.3),并通过重载解析(13.3)选择最佳构造函数。调用所选的构造函数以初始化对象,初始化表达式或表达式列表作为其参数。如果没有构造函数适用,或者重载决策是不明确的,则初始化是错误的。

    • Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source type to the destination type or (when a conversion function is used) to a derived class thereof are enumerated as described in 13.3.1.4, and the best one is chosen through overload resolution (13.3). If the conversion cannot be done or is ambiguous, the initialization is ill-formed. The function selected is called with the initializer expression as its argument; if the function is a constructor, the call initializes a temporary of the cv-unqualified version of the destination type. The temporary is a prvalue. The result of the call (which is the temporary for the constructor case) is then used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization. In certain cases, an implementation is permitted to eliminate the copying inherent in this direct-initialization by constructing the intermediate result directly into the object being initialized; see 12.2, 12.8.
    • 否则(即,对于剩余的复制初始化情况),可以如13.3中所述枚举可以从源类型转换为目的地类型或(当使用转换函数时)到其派生类的用户定义的转换序列。 1.4,通过重载决策(13.3)选择最好的一个。如果转换不能完成或不明确,则初始化是错误的。选择的函数以初始化表达式作为参数调用;如果函数是构造函数,则调用初始化目标类型的cv-nonqualified版本的临时函数。临时是一个prvalue。然后,根据上述规则,调用的结果(对于构造函数的情况是临时的)用于直接初始化作为复制初始化目标的对象。在某些情况下,允许实现通过将中间结果直接构造到正在初始化的对象中来消除此直接初始化中固有的复制;见12.2,12.8。

  • 如果目标类型是(可能是cv限定的)类类型:如果初始化是直接初始化,或者它是复制初始化,其中源类型的cv-nonqualified版本与类相同,或者是派生类,目的地的类,构造函数被考虑。列举了适用的构造函数(13.3.1.3),并通过重载解析(13.3)选择最佳构造函数。调用所选的构造函数以初始化对象,初始化表达式或表达式列表作为其参数。如果没有构造函数适用,或者重载决策是不明确的,则初始化是错误的。否则(即,对于剩余的复制初始化情况),可以如13.3中所述枚举可以从源类型转换为目的地类型或(当使用转换函数时)到其派生类的用户定义的转换序列。 1.4,通过重载决策(13.3)选择最好的一个。如果转换不能完成或不明确,则初始化是错误的。选择的函数以初始化表达式作为参数调用;如果函数是构造函数,则调用初始化目标类型的cv-nonqualified版本的临时函数。临时是一个prvalue。然后,根据上述规则,调用的结果(对于构造函数的情况是临时的)用于直接初始化作为复制初始化目标的对象。在某些情况下,允许实现通过将中间结果直接构造到正在初始化的对象中来消除此直接初始化中固有的复制;见12.2,12.8。

Since 0 and 1 are ints, not As, the copy initialization here falls under the second clause. The compiler find a user-defined conversion from int to A in your A::A(int) constructor, calls it to construct a temporary of type A, then performs direct initialization using that temporary. This, in turn, means the compiler is required to perform overload resolution for a constructor taking a temporary of type A, which selects your deleted move constructor, which renders the program ill-formed.

由于0和1是整数,而不是As,因此复制初始化属于第二个子句。编译器在A :: A(int)构造函数中找到用户定义的从int到A的转换,调用它来构造类型A的临时值,然后使用该临时值执行直接初始化。反过来,这意味着编译器需要对构造函数执行重载解析,该构造函数采用类型A的临时值,它选择已删除的移动构造函数,这会导致程序格式错误。

#2


11  

A a[2] = { 0, 1 };

Conceptually, this creates two temporary A objects, A(0) and A(1), and moves or copies them to initialise the array a; so a move or copy constructor is required.

从概念上讲,这会创建两个临时A对象A(0)和A(1),并移动或复制它们以初始化数组a;所以需要移动或复制构造函数。

As an optimisation, the move or copy is allowed to be elided, which is why your program doesn't appear to use the move constructor. But there must still be a suitable constructor, even if its use is elided.

作为优化,允许移除或复制,这就是您的程序似乎不使用移动构造函数的原因。但是,即使它的使用被省略,仍然必须有一个合适的构造函数。