此代码是否会导致未定义的行为:
#include <vector>
struct S
{
S() {}
int x;
};
int main()
{
std::vector<S> vec(5, S());
}
由于S()
默认初始化自动对象,因此其内容不会首先清零,因此x
将是不确定的。然后将包含不确定值的对象复制到每个矢量位置。
动机:我们可能期望它的行为与不是 UB 的std::vector<S> vec(5);
相同(自 C++11 以来),所以这将是一个容易意外犯的错误。
正如 Praetorian 在评论中提到的,在 C++11 之前,std::vector<S> vec(5);
可以自由地执行 5 次默认初始化,或者对部分或全部项目使用复制构造函数。
考虑到OP的以下评论:
向量 vec(5); 不是 UB(是吗?),我们倾向于认为 (5) 和 (5, S()) 应该做同样的事情。我可以看到这个错误是 很容易意外制造。
问题归结为如果:
vector<S> vec(5);
定义明确,那么为什么是:
std::vector<S> vec(5, S());
未定义的行为?
如果我们转到 std::vector::vector 的 cppreference 部分,我们可以看到在第一种情况下它(自 C++11 以来)(强调我的):
使用计数默认插入的 T 实例构造容器。 不制作副本。
而在第二种情况下:
使用具有值的元素的计数副本构造容器 价值。
第一种情况将默认构造元素,并且不会创建副本,而在第二种情况下,将制作副本,因此我们最终将x
复制到每个元素。由于 S
的默认构造函数不初始化x
,它将具有不确定的值,因此我们有未定义的行为,因为生成不确定值会调用未定义的行为。
自C++14年以来,第12
段8.5
节说:
如果评估生成不确定值,则行为 未定义,但以下情况除外
对于在这种情况下不适用的无符号字符,有一些例外情况。
我们知道x
在同一段中有一个不确定的值,该段落说:
存储具有自动或动态存储持续时间的对象时 获得,则对象具有不确定的值,如果没有 对对象执行初始化,该对象保留 不确定的值,直到该值被替换
TL;DR 是的,这是未定义的行为。
在代码中,对临时进行值初始化:
std::vector<S> vec(5, S());
// ^^^
从§8.5/10开始:
初始值设定项是一组空括号(即
()
)的对象应进行值初始化。
值初始化的定义是:
§8.5/8
对类型为
T
的对象进行值初始化意味着:— 如果
T
是一个(可能符合 cv 条件的)类类型(第 9 条),没有默认构造函数 (12.1) 或用户提供或删除的默认构造函数,则对象是默认初始化的;— [..]
在这种情况下,值初始化不包括零初始化,因为S
具有用户声明的默认构造函数。所以它是默认构造的。
§8.5/7
默认初始化类型
T
的对象意味着:— 如果
T
是(可能符合 cv 条件的)类类型(第 9 条),则考虑构造函数。枚举适用的构造函数 (13.3.1.3),并通过重载解析 (13.3) 选择初始值设定项()
的最佳构造函数。这样选择的构造函数被调用,使用空参数列表来初始化对象。— 如果 T 是数组类型,则每个元素都是默认初始化的。
— 否则,不执行初始化。
由于默认构造函数没有显式初始化x
,因此它保留了未初始化的垃圾值。默认的复制构造函数使用这些值初始化其他元素,即 UB。
§8.5/12
如果评估生成不确定的值,则行为未定义