初始化表达式可以使用变量本身吗?

考虑以下代码:

#include <iostream>

struct Data
{
    int x, y;
};

Data fill(Data& data)
{
    data.x=3;
    data.y=6;
    return data;
}

int main()
{
    Data d=fill(d);
    std::cout << "x=" << d.x << ", y=" << d.y << "
";
}

这里 d 是从 fill() 的返回值复制初始化的,但是 fill() 写入到 d 本身在返回结果之前.我担心的是 d 在被初始化之前是非平凡使用的,并且在某些(所有?)情况下使用未初始化的变量会导致未定义的行为.

Here d is copy-initialized from the return value of fill(), but fill() writes to d itself before returning its result. What I'm concerned about is that d is non-trivially used before being initialized, and use of uninitialized variables in some(all?) cases leads to undefined behavior.

那么这段代码是有效的,还是有未定义的行为?如果它是有效的,一旦 Data 停止成为 POD 或在其他情况下,行为是否会变得未定义?

So is this code valid, or does it have undefined behavior? If it's valid, will the behavior become undefined once Data stops being POD or in some other case?

推荐答案

这似乎不是有效的代码.它类似于问题中概述的情况:将 C++ 对象传递给它自己的构造函数是否合法?,尽管在??那个如果代码有效.机制并不相同,但基本推理至少可以让我们开始.

This does not seem like valid code. It is similar to the case outlined in the question: Is passing a C++ object into its own constructor legal?, although in that case the code was valid. The mechanics are not identical but the base reasoning can at least get us started.

我们从 缺陷报告 363 其中要求:

如果是这样,UDT 自初始化的语义是什么?例如

And if so, what is the semantics of the self-initialization of UDT? For example

 #include <stdio.h>

 struct A {
        A()           { printf("A::A() %p
",            this);     }
        A(const A& a) { printf("A::A(const A&) %p %p
", this, &a); }
        ~A()          { printf("A::~A() %p
",           this);     }
 };

 int main()
 {
  A a=a;
 }

可以编译打印:

A::A(const A&) 0253FDD8 0253FDD8
A::~A() 0253FDD8

提议的决议是:

3.8 [basic.life] 第 6 段表明这里的引用是有效的.允许在它之前获取类对象的地址已完全初始化,并且允许将其作为参数传递给一个引用参数,只要引用可以直接绑定即可.[...]

3.8 [basic.life] paragraph 6 indicates that the references here are valid. It's permitted to take the address of a class object before it is fully initialized, and it's permitted to pass it as an argument to a reference parameter as long as the reference can bind directly. [...]

因此,虽然 d 没有完全初始化,但我们可以将其作为引用传递.

So although d is not fully initialized we can pass it as a reference.

我们开始遇到麻烦的地方在这里:

Where we start to get into trouble is here:

data.x=3;

C++ 标准部分草案 3.8(缺陷报告引用的相同部分和段落)说(强调我的):

The draft C++ standard section 3.8(The same section and paragraph the defect report quotes) says (emphasis mine):

类似地,在对象的生命周期开始之前但在对象的生命周期开始之后对象将占用的存储空间已被分配,或者在对象的生命周期已经结束并且在存储之前被占用的对象被重用或释放,任何引用可以使用原始对象,但只能以有限的方式使用.对于一个对象正在建造或毁坏,见 12.7.否则,这样的glvalue指分配的存储(3.7.4.2),并使用不依赖于其值的 glvalue 是明确定义的.该程序在以下情况下具有未定义的行为:

Similarly, before the lifetime of an object has started but after the storage which the object will occupy has been allocated or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any glvalue that refers to the original object may be used but only in limited ways. For an object under construction or destruction, see 12.7. Otherwise, such a glvalue refers to allocated storage (3.7.4.2), and using the properties of the glvalue that do not depend on its value is well-defined. The program has undefined behavior if:

  • 一个左值到右值的转换 (4.1) 被应用于这样的左值,

  • an lvalue-to-rvalue conversion (4.1) is applied to such a glvalue,

glvalue 用于访问非静态数据成员或调用非静态成员函数对象,或

glvalue 绑定到对虚拟基类 (8.5.3) 的引用,或者

the glvalue is bound to a reference to a virtual base class (8.5.3), or

glvalue 用作 dynamic_cast (5.2.7) 的操作数或 typeid 的操作数.

the glvalue is used as the operand of a dynamic_cast (5.2.7) or as the operand of typeid.

那么访问是什么意思呢?缺陷报告 1531 将访问定义为:

So what does access mean? That was clarified with defect report 1531 which defines access as:

访问

读取或修改对象的值

to read or modify the value of an object

所以 fill 访问一个非静态数据成员,因此我们有未定义的行为.

So fill accesses a non-static data member and hence we have undefined behavior.

这也与 12.7 部分一致,其中说:

This also agrees with section 12.7 which says:

[...]形成一个指向(或访问对象 obj 的直接非静态成员的值,则应开始构建 obj并且它的销毁应该没有完成,否则指针值的计算(或访问成员值)导致未定义的行为.

[...]To form a pointer to (or access the value of) a direct non-static member of an object obj, the construction of obj shall have started and its destruction shall not have completed, otherwise the computation of the pointer value (or accessing the member value) results in undefined behavior.

由于您无论如何都在使用副本,因此您不妨在 fill 内创建一个 Data 实例并对其进行初始化.您避免必须通过 d.

Since you are using a copy anyway you might as well create an instance of Data inside of fill and initialize that. The you avoid having to pass d.

正如 T.C. 指出的那样.明确引用生命周期何时开始的细节很重要.来自 3.8 部分:

As pointed out by T.C. it is important to explicitly quote the details on when lifetime starts. From section 3.8:

对象的生命周期是对象的运行时属性.一个如果对象属于一个类,则称该对象具有非平凡的初始化或聚合类型,并且它或其成员之一由构造函数而不是普通的默认构造函数.[ 笔记:由平凡的复制/移动构造函数初始化是不平凡的初始化.― 尾注] T 类型对象的生命周期开始时间:

The lifetime of an object is a runtime property of the object. An object is said to have non-trivial initialization if it is of a class or aggregate type and it or one of its members is initialized by a constructor other than a trivial default constructor. [ Note: initialization by a trivial copy/move constructor is non-trivial initialization. ― end note ] The lifetime of an object of type T begins when:

  • 获得了适合类型 T 的对齐方式和大小的存储,并且

  • storage with the proper alignment and size for type T is obtained, and

如果对象有非平凡初始化,则其初始化完成.

if the object has non-trivial initialization, its initialization is complete.

初始化很重要,因为我们是通过复制构造函数进行初始化的.

The initialization is non-trivial since we are initializing via the copy constructor.

相关文章