请考虑以下代码:
class A {
private:
std::string s;
public:
A() = delete;
A(const A&) = delete;
A(A&&) = delete;
A(const std::string &a) : s(a) {}
};
现在,我想初始化一个使用列表初始化的A数组。 g ++(4.9.1)可以成功构建以下代码:
int main() {
A arr[2] = {{"a"}, {"b"}};
return 0;
}
但是,以下代码失败了:
class Aggr {
private:
A arr[2];
public:
Aggr() : arr{{"a"}, {"b"}} {}
};
错误消息是,
test.cc: In constructor ‘Aggr::Aggr()’:
test.cc:22:28: error: use of deleted function ‘A::A(A&&)’
Aggr() : arr{{"a"}, {"b"}} {}
^
test.cc:11:3: note: declared here
A(A&&) = delete;
^
也就是说,list-initializer试图调用一个移动构造函数来初始化一个类内部的数组。但是,该代码是由clang v3.5成功构建的,没有任何警告。 所以,我想知道C ++ 11(或更高版本)有关列表初始化的规则。提前谢谢。
答案 0 :(得分:2)
一遍又一遍地阅读标准,我认为这是一个错误。
标准说什么?
8.5.1 / 2 :当初始化列表初始化聚合时,如8.5.4中所述,初始化列表的元素被采用 作为聚合成员的初始化者,正在增加 下标或成员顺序。每个成员都是从中复制初始化的 相应的初始化条款。
解释说:
8.5 / 14 :( ...)称为复制初始化。 [注意:复制初始化可以调用移动(12.8)。 - 后注]
但我在12.8中没有发现任何证据表明在您的具体情况下需要采取行动。
8.5.4 / 3 否则,如果T是类类型,则考虑构造函数。如果T有一个初始化列表构造函数,那么参数 list包含初始化列表作为单个参数;除此以外, 参数列表由初始化列表的元素组成。 枚举适用的构造函数,并选择最佳构造函数 通过重载决议(13.3)。
所以原则上你的代码应该可行!
这是一个错误吗?尝试实验方式
我注释掉了移动构造函数的删除,以便从隐式移动构造函数中受益。奇怪的是,我收到以下错误消息:
Compilation error time: 0 memory: 3232 signal:0
prog.cpp: In constructor 'Aggr::Aggr()':
prog.cpp:19:28: error: use of deleted function 'A::A(const A&)'
Aggr() : arr{{"a"}, {"b"}} {}
^
prog.cpp:10:3: note: declared here
A(const A&) = delete
所以现在他抱怨缺少复制构造函数!
更奇怪的是,我随后提供了自己的移动构造函数而不是隐式构造函数:这里它成功编译了代码!
最后,我提供了副本和移动,并添加了一些跟踪:
class A {
private:
std::string s;
public:
A() = delete;
A(const A&) { std::cout<<"copy\n";} //= delete;
A(A&&) { std::cout<<"move\n";} //= delete;
A(const std::string &a) : s(a) { std::cout<<"string ctor\n";}
};
当我创建一个Aggr
对象时,它只显示:
string ctor
string ctor
显示数组成员是使用copy elision从字符串构造函数初始化的,正如我们所期望的那样。
所有这些测试都是在带有C ++ 14选项的ideone上使用gcc-9.4.2进行的。
<强>结论强>
相同的代码无法使用隐式移动ctor进行编译并且使用用户定义的移动ctor成功的事实看起来非常严重,就像一个错误。
移动构造函数在可用时未使用的事实强化了这种印象。
因此,我已经报告了this bug。