2

当一个类成员在构造的那一刻不能有合理的意义时,我不会初始化它。显然,这只适用于 POD 类型,您不能使用构造函数初始化对象。

这样做的好处是,除了节省 CPU 周期,将某些东西初始化为一个没有意义的值之外,我可以使用 valgrind 检测这些变量的错误使用;当我只是给这些变量一些随机值时,这是不可能的。

例如,

struct MathProblem {
  bool finished;
  double answer;

  MathProblem() : finished(false) { }
};

在数学问题解决(完成)之前,没有答案。answer提前初始化(到零)是没有意义的,因为这可能不是答案。只有在设置为 trueanswer之后才有意义。finished

因此,在初始化之前使用answer是一个错误,并且完全可以成为 UB。

但是,初始化之前的一个简单副本answer当前也是 UB(如果我正确理解标准),这没有意义:默认的复制和移动构造函数应该能够简单地制作一个简单的副本(又名,如-如果使用memcpy),是否已初始化:我可能想将此对象移动到容器中:

v.push_back(MathProblem());

然后使用容器内的副本。

移动具有未初始化的、可简单复制的成员的对象是否确实被标准定义为 UB?如果是这样,为什么?这似乎没有意义。

4

2 回答 2

1

移动具有未初始化的、可简单复制的成员的对象是否确实被标准定义为 UB?

取决于成员的类型。标准 说:

[基本.indet]

当获得具有自动或动态存储持续时间的对象的存储时,该对象具有一个不确定的值,如果没有对该对象执行初始化,则该对象将保留一个不确定的值,直到该值被替换([expr.ass])。

如果评估产生不确定的值,则行为未定义,但以下情况除外:

  • 如果通过以下评估产生无符号普通字符类型 ([basic.fundamental]) 或 std​::​byte 类型 ([cstddef.syn]) 的不确定值:

    • 条件表达式的第二个或第三个操作数,
    • 逗号表达式的右操作数,
    • 转换或转换([conv.integral]、[expr.type.conv]、[expr.static.cast]、[expr.cast])到无符号普通字符类型或 std​::​byte 类型的操作数([cstddef.syn]),或
    • 丢弃值表达式,

    那么运算的结果是一个不确定的值。

  • 如果通过对简单赋值运算符 ([expr.ass]) 的右操作数求值产生无符号普通字符类型或 std​::​byte 类型的不确定值,其第一个操作数是无符号普通字符类型的左值,或std​::​byte 类型,不确定的值替换左操作数引用的对象的值。

  • 如果在初始化无符号普通字符类型的对象时,初始化表达式的评估产生了无符号普通字符类型的不确定值,则该对象被初始化为不确定值。如果在初始化 std​::​byte 类型的对象时通过初始化表达式的评估产生了无符号普通字符类型或 std​::​byte 类型的不确定值,则该对象被初始化为不确定值。

没有任何例外情况适用于您的示例对象,因此 UB 适用。


用 memcpy 是 UB 吗?

它不是。std::memcpy将对象解释为字节数组,在这种特殊情况下没有 UB。如果您尝试阅读不确定的副本,您仍然有 UB(除非上述例外情况适用)。


为什么?

C++ 标准不包括大多数规则的基本原理。自第一个标准以来,此特定规则就已存在。它比有关陷阱表示的相关 C 规则稍微严格一些。据我了解,没有既定的陷阱处理约定,作者也不希望通过指定它来限制实现,而是选择将其指定为 UB。这还具有允许优化器推断永远不会读取不确定值的效果。


我可能想将此对象移动到容器中:

将未初始化的对象移动到容器中通常是一个逻辑错误。目前还不清楚你为什么想做这样的事情。

于 2021-03-20T08:20:05.260 回答
1

C++ 标准的设计深受 C 标准的影响,其作者(根据已发布的基本原理)打算并期望实现将在实现质量的基础上,通过有意义地处理程序来扩展语言的语义即使标准没有“正式”定义这些程序的行为,这样做也很明显是有用的。因此,这两个标准都更加重视确保它们不强制要求行为,因为这样做可能会使某些实现变得不那么有用,而不是确保它们强制要求质量通用实现应该支持的所有内容。

在许多情况下,通过保证memcpy在任何有效的存储区域上使用,在最坏的情况下,其行为方式与使用一些可能无意义的位模式填充目标一致,从而扩展语言的语义可能对实现很有用没有外部副作用,很少有如果有的话,让它做其他事情会更容易或更有用。任何人都应该关心的行为是否在memcpy涉及有效存储区域的特定情况下定义的唯一情况是,在这些情况下,某些替代行为确实比普通行为更有用。如果存在这种情况,编译器编写者及其客户将比委员会更好地判断哪种行为最有用。

作为替代行为可能更有用的情况的示例,请考虑memcpy用于复制部分编写的结构的代码,然后使用它来制作该结构的两个副本。在某些情况下,让编译器只编写两个目标结构的原始部分可能会提高效率,但这种行为会明显不同于第一个memcpy表现得好像它将一些位模式存储到其目的地。请注意,如果没有以影响行为的方式使用结构的未初始化部分的副本,则这种更改不会对程序的整体行为产生不利影响,标准没有很好的方法来区分可能发生或不可能发生的场景在这样的模块下,因此未定义所有此类场景。

于 2021-03-23T17:43:20.133 回答