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

j2qf4p5b  于 11个月前  发布在  其他
关注(0)|答案(3)|浏览(94)

下面是一个例子:

#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或在其他情况下,行为会变成未定义的吗?

vecaoik1

vecaoik11#

这看起来不像是有效的代码。它类似于问题中概述的情况:Is passing a C++ object into its own constructor legal?,尽管在这种情况下代码是有效的。机制并不相同,但基本的推理至少可以让我们开始。
我们从缺陷报告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)应用于这样的GL值,
    *glvalue用于访问非静态数据成员或调用对象的非静态成员函数,或
  • glvalue绑定到对虚基类的引用(8.5.3),或者
  • glvalue用作dynamic_cast(5.2.7)的操作数或typeid的操作数。

那么,access 是什么意思呢?缺陷报告1531对此进行了澄清,它将access定义为:
接入
读取或修改对象的值
所以fill * 访问 * 一个非静态数据成员,因此我们有未定义的行为。
这也与第12.7节一致,该节说:
[...]要形成指向对象obj的直接非静态成员的指针(或访问其值),obj的构造必须已经开始,其析构必须尚未完成,否则指针值的计算(或访问成员值)将导致未定义的行为。
既然你使用的是一个副本,你可以在fill中创建一个Data示例并初始化它,这样就避免了传递d
正如T.C.所指出的,明确引用生命周期开始的细节是很重要的。来自第3.8节:
一个对象的生命周期是对象的运行时属性。如果一个对象是类或聚合类型的,并且它或它的一个成员是由一个构造函数而不是平凡的默认构造函数初始化的,那么这个对象被称为具有非平凡的初始化。[注:平凡的复制/移动构造函数的初始化是非平凡的初始化。-结束注] T类型的对象的生命周期开始于:

  • 获得具有用于类型T的适当对准和大小的存储,并且
  • 如果对象具有非平凡初始化,则其初始化完成。

初始化是不平凡的,因为我们是通过复制构造函数初始化的。

slhcrj9b

slhcrj9b2#

我没看到问题。如果未初始化的整数成员是有效的,因为你是为了写而访问的。* 阅读 * 它们会导致UB。

new9mtju

new9mtju3#

我认为这是有效的(疯狂,但有效)。
这将是法律的和逻辑上可以接受的:

Data d ;

d = fill( d ) ;

字符串
事实上,这个形式是一样的:

Data d = fill( d ) ;


就语言的逻辑结构而言,这两个版本是等效的。
所以这是法律的,逻辑上对语言是正确的。
然而,由于我们通常希望人们在创建变量时将其初始化为默认值(出于安全考虑),这是 * 糟糕的编程实践 *。
有趣的是,g++ -Wall编译这段代码时没有blurp。

相关问题