请考虑以下代码:
#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 << "\n";
}
此处d
从fill()
的返回值进行了初始化,但fill()
会在返回结果之前写入d
。我关注的是d
在初始化之前是非常简单的,在某些(所有?)情况下使用未初始化的变量会导致未定义的行为。
这段代码有效,还是有未定义的行为?如果它有效,一旦Data
停止为POD或某些其他情况,行为是否会变为未定义?
答案 0 :(得分:13)
这似乎不是有效的代码。它类似于问题中概述的情况:Is passing a C++ object into its own constructor legal?,尽管在这种情况下代码是有效的。机制并不完全相同,但基本推理至少可以让我们开始。
我们从defect report 363开始询问:
如果是这样,UDT的自我初始化的语义是什么? 例如
#include <stdio.h> struct A { A() { printf("A::A() %p\n", this); } A(const A& a) { printf("A::A(const A&) %p %p\n", this, &a); } ~A() { printf("A::~A() %p\n", this); } }; int main() { A a=a; }
可以编译和打印:
A::A(const A&) 0253FDD8 0253FDD8 A::~A() 0253FDD8
并且提议的决议是:
3.8 [basic.life]第6段表明此处的引用是有效的。允许在它之前获取类对象的地址 已完全初始化,并允许将其作为参数传递给 参考参数,只要引用可以直接绑定。 [...]
因此虽然d
未完全初始化,但我们可以将其作为参考传递。
我们开始遇到麻烦的地方就在这里:
data.x=3;
草案C ++标准部分3.8
(缺陷报告引用的相同部分和段落)说(强调我的):
同样,在对象的生命周期开始之前但在之后 对象占用的存储空间已被分配,或之后 对象的生命周期已经结束,在存储之前就已经存在了 对象占用被重用或释放,任何引用的glvalue 可以使用原始对象,但仅限于有限的方式。对于一个对象 正在建设或破坏,见12.7。否则,这样的glvalue 指分配存储(3.7.4.2),并使用的属性 不依赖于其值的glvalue是明确定义的。该程序 在以下情况下具有未定义的行为:
左值到右值的转换(4.1)适用于这样的glvalue,
glvalue用于访问非静态数据成员或调用非静态成员函数 对象,或
glvalue绑定到对虚基类的引用(8.5.3),或
glvalue用作dynamic_cast(5.2.7)的操作数或typeid的操作数。
那么访问是什么意思?用defect report 1531澄清了这一点,将访问定义为:
访问
读取或修改对象的值
所以fill
访问非静态数据成员,因此我们有未定义的行为。
这也同意12.7
部分的说法:
[...]形成指向(或 访问对象obj的直接非静态成员的值,obj的构造应该已经开始 并且它的销毁不应该完成,否则计算指针值(或访问 成员值)导致未定义的行为。
由于您无论如何都在使用副本,因此您也可以在fill
内创建一个数据实例并对其进行初始化。你不必通过d
。
正如T.C.所指出的那样。明确引用生命周期开始时的详细信息非常重要。来自3.8
部分:
对象的生命周期是对象的运行时属性。一个 如果对象属于类,则称其具有非平凡的初始化 或者聚合类型,它或它的一个成员由a初始化 除了一个普通的默认构造函数之外的构造函数。 [ 注意: 通过简单的复制/移动构造函数进行初始化是非常重要的 初始化。 - 尾注] T类对象的生命周期 从以下时间开始:
获得具有类型T的正确对齐和大小的存储,并且
如果对象具有非平凡的初始化,则其初始化完成。
由于我们通过复制构造函数进行初始化,因此初始化非常重要。
答案 1 :(得分:3)
我没有看到问题。访问未初始化的整数成员是有效的,因为您正在访问以进行编写。 阅读它们会导致UB。
答案 2 :(得分:-5)
我认为这是有效的(疯狂但有效)。
这既合法又符合逻辑:
Data d ;
d = fill( d ) ;
事实是这种形式是相同的:
Data d = fill( d ) ;
就语言的逻辑结构而言,这两个版本是等价的。
因此,该语言在法律上和逻辑上都是正确的。
但是,正如我们通常希望人们在创建变量时将变量初始化为默认值(为安全起见),这是糟糕的编程习惯。
有趣的是,g ++ -Wall编译此代码时没有模糊。