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