可以初始化表达式使用变量本身? [英] Can initializing expression use the variable itself?

查看:161
本文介绍了可以初始化表达式使用变量本身?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

请考虑以下代码:

  #include< iostream> 

struct Data
{
int x,y;
};

数据填充(数据和数据)
{
data.x = 3;
data.y = 6;
return data;
}

int main()
{
数据d = fill(d);
std :: cout<< x =<< d.x<< ,y =< d.y < \\\
;
}

这里 d fill()的返回值复制初始化,但 fill()写入 d 本身,然后返回其结果。我关心的是 d 在被初始化之前是非平凡的,在一些(所有?)情况下使用未初始化的变量会导致未定义的行为。 p>

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

解决方案

这似乎不是有效的代码。它与问题中概述的情况类似:是否将C ++对象传递到自己的构造函数中?,但是case代码有效。



我们从缺陷报告363 ,其中询问:


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

  #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段表示这里的引用是有效的。允许在
完全初始化之前获取类对象的地址,并且允许将它作为参数传递给
引用参数,只要引用可以直接绑定。
[...]


所以虽然 d 完全初始化后,我们可以将其作为参考。



我们开始遇到麻烦的是这里:

  data.x = 3; 

草稿C ++标准部分 3.8

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




  • 左值到右值转换glvalue,


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


  • glvalue绑定到对虚拟基类(8.5.3)或

    li>
  • glvalue用作dynamic_cast(5.2.7)的操作数或作为typeid的操作数。



那么访问是什么意思?已通过定义访问权限的缺陷报告1531 进行了澄清as:


存取


修改对象的值



因此填写

这也符合 12.7节的规定其中说:


[...]要形成指向(或
)的指针值)的对象obj的直接非静态成员,obj的构造应该已经启动
并且其销毁将不会完成,否则计算指针值(或访问
成员值)会导致未定义的行为。


因为你使用一个副本,你可能会创建一个< $ c> fill 并初始化它。你不必通过 d



正如T.C.重要的是明确地引用关于生命周期开始的细节。来自 3.8 :


对象的生命周期是目的。如果一个
对象是类
或聚合类型,并且它的一个成员由除了一个琐碎的默认构造函数之外的
构造函数初始化,则
对象被认为具有非平凡的初始化。 [注意:
初始化一个简单的复制/移动构造函数是非平凡的
初始化。 - end note] T
类型的对象的生命周期开始于:




  • 获得类型T的大小,并且


  • 如果对象具有不重要的初始化,则其初始化完成。



由于我们正在通过拷贝构造函数初始化,所以初始化并不重要。


Consider the following code:

#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";
}

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.

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?

解决方案

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.

We start with defect report 363 which asks:

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\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;
 }

can be compiled and prints:

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

and the proposed resolution was:

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. [...]

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;

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

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:

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

  • the glvalue is used to access a non-static data member or call a non-static member function of the object, or

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

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

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

access

to read or modify the value of an object

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

This also agrees with section 12.7 which says:

[...]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.

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.

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

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:

  • 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.

这篇关于可以初始化表达式使用变量本身?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆