未初始化对象的成员地址是否已明确定义?

问题描述 投票:0回答:3

考虑以下示例。当构造

bar
时,它会为其基类型 (
foo
) 构造函数提供
my_member.y
的地址,其中
my_member
是尚未初始化的数据成员。

struct foo {
    foo(int * p_x) : x(p_x) {}
    int * x;
};

struct member {
    member(int p_y) : y(p_y) {}
    int y;
};

struct bar : foo
{
    bar() : foo(&my_member.y), my_member(42) {}
    member my_member;
};

#include <iostream>

int main()
{
    bar my_bar;
    std::cout << *my_bar.x;
}

这个定义明确吗?获取未初始化对象的数据成员的地址是否合法?我发现关于传递对未初始化对象的引用的this Question,但它不完全相同。在本例中,我在未初始化的对象上使用 成员访问运算符

.

确实不应通过初始化来更改对象数据成员的地址,但这并不一定会使“获取”该地址得到明确定义。此外,ccpreference.com 的 member access Operators 页面有这样的说法:

即使没有必要,也会评估两个运算符的第一个操作数(例如,当第二个操作数命名静态成员时)。

我理解这意味着在
&my_member.y

的情况下,将评估

my_member
,我认为这很好(就像
int x; x;
似乎很好),但我也找不到支持这一点的文档。
    

c++ language-lawyer object-lifetime ctor-initializer member-access
3个回答
7
投票

您所做的不是使用未初始化的对象,而是使用不在其生命周期内的对象。

my_member

是在

foo
之后构造的,因此
my_member
的生命周期尚未从
foo(&my_member.y)
开始。
来自

[基本生活]

在对象的生命周期开始之前,但在分配该对象将占用的存储空间之后[...],可以使用任何引用原始对象的泛左值,但只能以有限的方式使用。 [...]这样的泛左值指的是分配的存储,并且使用不依赖于其值的泛左值的属性是明确定义的。如果出现以下情况,则程序具有未定义的行为:

泛左值用于访问对象,或者[...]
这里访问它特指读取或修改对象的值。

my_member

的求值会产生一个左值,并且不需要转换为纯右值,因此它仍然是一个左值。同样,

my_member.y
的评估也是左值。然后我们得出结论,没有对象的值被访问,这是明确定义的。
    


2
投票
&my_member.y

传递给

foo
的构造函数,甚至可以复制指针 - 您可以使用
x(p_x)
进行操作。

尽管在 
foo

的构造函数中,

取消引用该指针的行为未定义。 (但你不这样做。)

令人惊讶的是,在代码中使用 

&my_member.y

0
投票
[class.cdtor]

第 1 段中找到,其中指出:


对于具有非平凡构造函数的对象,在构造函数开始执行之前引用该对象的任何非静态成员或基类会导致未定义的行为。 [...]

为了进一步说明这一点,请考虑以下示例:

struct W { int j; }; struct X : public virtual W { }; struct Y { int* p; X x; Y() : p(&x.j) { // undefined, x is not yet constructed } };

在您的代码中,

member
 结构具有一个重要的构造函数,并且 
y

是一个非静态成员,因此在初始化之前获取其地址时会导致未定义的行为。但是,使用

my_member
本身 (
&my_member
) 的地址是允许的,因为它不直接引用任何非静态成员或基类。
最初,这条规则对我来说似乎不直观,假设对象本身包含它们的非静态成员和基类。然而,在深入研究 WG21(ISO C++ 委员会)的历史文档后,我从
N0804
中发现了相关信息:

[...] 规则 [...] 被采用是因为人们希望允许基类和非 POD 类的成员在堆上分配。

嗯,(至少)有一定道理。

© www.soinside.com 2019 - 2024. All rights reserved.