为啥我可以从 初始化常规数组,而不是 std::array

Posted

技术标签:

【中文标题】为啥我可以从 初始化常规数组,而不是 std::array【英文标题】:Why can I initialize a regular array from , but not a std::array为什么我可以从 初始化常规数组,而不是 std::array 【发布时间】:2015-09-25 03:24:40 【问题描述】:

这行得通:

int arr[10] = ;

arr 的所有元素的值初始化为零。

为什么这不起作用:

std::array<int, 10> arr(); 

我从 g++(4.8.2 版)收到以下警告:

警告:缺少成员“std::array::_M_elems”的初始化程序

【问题讨论】:

“这不起作用...我收到以下警告” 所以它起作用了。如果它不起作用,它就不会编译! @JonathanWakely 我相信 OP 试图通过警告来表达惊讶,我也发现它令人惊讶。这确实是有问题的,因为您使用的是-Werror,我这样做了。很高兴看到警告已在最新版本中删除,但这对那些无法升级的人没有帮助:-( @ShafikYaghmour,但是 OP 没有使用 -Werror(或者它不会说“警告”)并且在不明智地使用 -Wno-xxxx 的情况下不分青红皂白地使用 -Werror 不一定是好的主意。我知道警告令人惊讶,但它确实有效,所有元素都按预期初始化为零。 (尽管正如 AnT 指出的那样,使用() 很奇怪,应该不鼓励,如果你这样做,孩子们会在街上指着你笑)。 @JonathanWakely 是的,同意,使用-Wno-xxxx 很有用,但在这种情况下,我可能不想使用-Wno-missing-field-initializers,因为我可能想要它警告的其他情况。同意() 确实很奇怪,但如果将示例更改为std::array&lt;int, 10&gt; arr = ; ,问题仍然存在。 ... 并且可以说是一个答案,唯一指出() 很奇怪并不是真正的答案。可能被认为是一个糟糕的答案,因为它现在阻止了对问题的合理编辑,这将简化它并删除不是真正的核心问题。 【参考方案1】:

有两个问题,一个是风格问题和警告。

虽然可能不明显,聚合初始化是在临时发生的,然后用作复制构造函数的参数。更惯用的初始化方法如下:

std::array<int, 10> arr = ; 

虽然这仍然会留下警告。

gcc bug report: - -Wmissing-field-initializers relaxation request 覆盖了警告,其中一个 cmets 说:

[...]当然,说 MyType x = ; 的 C++ 语法应该支持, 如此处所示:

http://en.cppreference.com/w/cpp/language/aggregate_initialization

例如在哪里:

struct S 
  int a;
  float b;
  std::string str;
;

S s = ; // identical to S s = 0, 0.0, std::string;

由于之前的 cmets 中所述的原因,这不应该发出警告。

随后的评论说:

我关于零初始化的说法不准确(谢谢),但是 一般的观点仍然存在:在 C 中你必须写 '= 0' 因为 该语言不支持空大括号初始化程序(您会得到一个 带有-pedantic的警告);在 C++ 中,您可以编写 '= ' 或 'T foo = T();',但你不需要专门写'= 0'。

最新版本的 gcc 不会针对这种情况产生此警告,see it live working with gcc 5.1。

我们可以看到该主题也包含在 thead 中的 Clang 开发人员列表中:-Wmissing-field-initializers。

参考草案 C++11 标准部分 8.5.1[dcl.init.aggr] 说:

如果列表中的初始化子句少于 聚合中的成员,然后每个成员未显式初始化 应从一个空的初始化列表(8.5.4)初始化。 [ 示例:

struct S  int a; const char* b; int c; ;
S ss =  1, "asdf" ;

将 ss.a 初始化为 1,将 ss.b 初始化为“asdf”,将 ss.c 初始化为 int() 形式的表达式,即 0。 —end example ]

所以这是有效的 C++,尽管如使用 所指出的那样,它不是有效的 C99。有人可能会争辩说这只是一个警告,但这似乎是使用 进行聚合初始化的惯用 C++,如果我们使用 -Werror 将警告转化为错误,则会出现问题。

【讨论】:

我刚刚在自己的代码中遇到了这个问题,不久前正在做研究。 从你的cmets上看这个问题,这个问题让你有些苦恼。我在下面的回答提供了一个简单的解决方案。【参考方案2】:

首先,您可以() 初始化器与std::array 对象一起使用,但从语义上讲,它代表使用来自临时值初始化的std::array 对象的复制构造函数进行直接初始化,即相当于

std::array<int, 10> arr(std::array<int, 10>); 

它实际上应该编译。

其次,当你可以做的时候,你真的不必走()的方式

std::array<int, 10> arr = ;

std::array<int, 10> arr;

两者中的第一个在语法上与您的int arr[10] = ; 最相似,这让我想知道您为什么一开始没有尝试。为什么在构建= 语法的std::array 版本时决定使用() 而不是=

【讨论】:

注意,它会编译,它只是一个警告,如果你使用-Werror,这是一个问题。建议的替代方案都不会删除警告see it live。归根结底,它仍然归结为聚合初始化和一个不必要的警告,说明什么应该被认为是初始化聚合的惯用方式。 因为我(错误地)认为这会调用默认构造函数。看到这个帖子:***.com/questions/31278377/… @ShafikYaghmour C++ 标准不区分“警告”和“错误”。常见的编译器在这方面也不一致;例如,gcc 会针对某些违反约束以及某些正确代码给出“警告”。因此,某人忽略一条消息是不正确的,因为它“只是一个警告”;问题要先搞清楚。 @MattMcNabb 同意,但不确定这与我的评论有何关联。他们没有违反这里的标准,我也没有说他们违反了,但这是一个令人惊讶的警告,一旦提交错误报告,警告就被删除了。【参考方案3】:

在使用-Werror 编译时,已经有足够多的人指出这是一个“问题”,我认为值得一提的是,如果你只是加倍,问题就会消失:

std::array<int, 10> arr;

在 gcc 4.9.2 上不会对我产生任何警告。

补充一点为什么这可以解决它:我的理解是 std::array 实际上是一个以 C 数组作为其唯一成员的类。所以在大括号上加倍是有意义的:外大括号表示您正在初始化类,然后内大括号默认初始化该类的唯一成员。

由于类中只有一个变量时不会产生歧义,因此只使用一对 应该是合理的,但 gcc 在这里过于迂腐,并发出警告。

【讨论】:

以上是关于为啥我可以从 初始化常规数组,而不是 std::array的主要内容,如果未能解决你的问题,请参考以下文章

为啥使用 std::vector 而不是 realloc? [关闭]

为啥要引入 `std::launder` 而不是让编译器处理它?

为啥 std::apply 可以调用 lambda 而不是等效的模板函数?

为啥我们不能通过值传递数组来函数?

为啥在这个程序中使用指针而不是常规变量?

为啥在 std::copy 期间使用 std::back_inserter 而不是 end()?