这是代码示例,其中Test
是带有某些成员和用户定义的构造函数的不可复制且不可移动的类virtual
,并且B
是包含原始(C样式)Test
对象数组的类:
class Test
{
public:
Test() = delete;
Test(const Test&) = delete;
Test(Test&&) = delete;
Test& operator=(const Test&) = delete;
Test& operator=(Test&&) = delete;
Test(int a, int b) : a_(a), b_(b) {}
virtual ~Test() {}
int a_;
int b_;
};
//----------------
class B
{
public:
/*(1)*/ B() : test_{{1, 2}, {3, 4}} {} // Does not compile on GCC, but compiles on Clang and MSVC
private:
Test test_[2];
};
//----------------
int main()
{
B b;
/*(2)*/ Test test[2] = {{1, 2}, {3, 4}}; // Successfully compiles on GCC, Clang and MSVC
}
我想使用加括号的初始化语法(第行)来初始化B
的内部数组,这样就可以就地构造两个对象中的每个对象,而无需创建一个临时对象然后移动它。test_
/*1*/
Test
在Clang和MSVC上,此代码无需警告即可编译。
但是GCC的行为使我感到困惑:它无法编译行/*1*/
,而成功地编译了行/*2*/
,而我使用相同的语法来初始化本地数组。但是,对于编译第一行,它仍然需要删除class的move构造函数Test
。
问题是,为什么?C ++标准是否明确定义了这些行/*1*/
,是否/*2*/
应该进行编译?如果可以,那么从标准的角度来看,哪个编译器是正确的?可以将这种不一致的行为称为GCC错误,还是Clang和MSVC忽略它们应该执行的某些检查?
我可以理解,GCC可能需要一个move构造函数才能Test
从内部花括号({1, 2}
)创建一个临时对象,然后将该对象移动到数组中。因此,编译错误。但是,如果是这样的话,为什么出于同样的原因它也不会失败/*(2)*/
呢?在此示例中,这是我最困惑的事情。
顺便说一句,这是一个有趣的观察:如果我将test_
with的定义替换为std::array<Test, 2>
(而不是“ C样式”数组),并用替换构造函数的初始化列表中的代码,则test_{{{1, 2}, {3, 4}}}
所有内容将开始在上述所有三个编译器上成功编译。
我还不清楚为什么在这种情况下GCC在任何情况下都不会失败,而“原始”数组会失败。
谁能解释一下?
我认为初始化没有问题,所以我认为这是一个GCC错误。
涉及的初始化是列表初始化,因此我们参考[dcl.init.list] / 3:
对象或类型引用的列表初始化
T
定义如下:
[...]
(3.3)否则,如果
T
是聚合,则执行聚合初始化。[...]
(数组是一个集合。)现在,我们转到[dcl.init.aggr] / 3:
当按[dcl.init.list]中指定的初始化器列表初始化聚合时,初始化器列表的元素按顺序用作聚合器元素的初始化器。每个元素都是从相应的initializer-clause复制初始化的。如果初始化子句是一个表达式,并且需要缩小转换范围以转换该表达式,则程序格式错误。
因此,对于这两个元素中的任何一个,我们都在有效地进行Test a = {1, 2}
,这是有效的,因为Test(int, int)
它不是显式的。因此,初始化格式正确,应由编译器接受。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句