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

Sap*_*Sap 4 c++ std c++11 c++14

为什么?!为什么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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

如果删除移动构造函数,则无法编译代码(' 使用已删除的函数'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 };
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

Mik*_*our 11

A a[2] = { 0, 1 };
Run Code Online (Sandbox Code Playgroud)

概念上,这创建了两个临时A的物体,A(0)并且A(1),和移动或复制它们来初始化所述阵列a; 所以需要移动或复制构造函数.

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

  • @ user3544995因为复制/移动elision是可选的优化.事实上,只要不影响程序的执行,编译器就可以省略任何东西(这称为*as-if规则*).明确提到复制/移动构造函数的唯一原因是,如果复制/移动构造函数有副作用(这会影响程序的执行),它甚至会发生. (3认同)

T.C*_*.C. 6

A a[2] = { 0, 1 };
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

由于01intS,不A秒,拷贝初始化这里的第二个条款下下降.编译器找到一个用户定义的转换intA你的A::A(int)构造函数,调用它来构建一个临时的类型A,然后执行使用临时直接初始化.反过来,这意味着编译器需要对采用临时类型的构造函数执行重载解析,该构造函数A选择已删除的移动构造函数,这会导致程序格式错误.