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

时间:2014-07-10 14:05:52

标签: c++ c++11 std 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;
}

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

  

idx = 0的构造函数   idx = 1的构造函数   具有idx = 1的析构函数   具有idx = 0的析构函数

如果删除移动构造函数,则无法编译代码(&#39; 使用已删除的函数'A :: A(A&amp;&amp;)'&#39;。但如果构造函数未删除它不使用! 多么愚蠢的限制? 注意: 我为什么需要它?当我尝试初始化包含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 };
}

如果您尝试使用unique_ptr的向量,情况会更糟。

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

P.S。我已经创建了更具体和明确的问题 - 欢迎来到discuss它!

2 个答案:

答案 0 :(得分:11)

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

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

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

答案 1 :(得分:6)

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

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

  

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

反过来,

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

  
      
  • [...]
  •   
  • 如果目标类型是(可能是cv限定的)类类型:   
        
    • 如果初始化是直接初始化,或者是复制初始化,那么cv-nonqualified版本的源   type与类的类相同,或者是类的派生类   目的地,建设者被考虑。适用的构造函数   列举(13.3.1.3),并通过过载选择最好的一个   决议(13.3)。如此选择的构造函数被调用以初始化   对象,初始化表达式或表达式列表作为其对象   参数(一个或多个)。如果没有构造函数适用,或者重载解析是   暧昧,初始化是不正确的。
    •   
    • 否则(即,对于剩余的复制初始化情况),可以从源转换的用户定义的转换序列   键入目标类型或(使用转换函数时)   按照13.3.1.4的描述列举其派生类别,   并且通过重载决策(13.3)选择最好的一个。如果   转换不能完成或模糊,初始化是   病态的。使用初始化程序调用所选的函数   表达作为其论点;如果函数是构造函数,则调用   初始化一个临时的cv-nonqualified版本的   目的地类型。临时是一个prvalue。通话的结果   (然后用于构造函数的临时情况)   根据上面的规则,直接初始化对象   复制初始化的目的地。在某些情况下,   允许实现消除此中固有的复制   通过直接构造中间结果直接初始化   进入被初始化的对象;见12.2,12.8。
    •   
  •   

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