简体   繁体   English

C ++使用对正在定义的变量的引用

[英]C++ Using a reference to the variable being defined

Is the following code valid C++, according to the standard (discounting the ...s)? 以下代码是否有效C ++,根据标准(折扣... s)?

bool f(T& r)
{
    if(...)
    {
        r = ...;
        return true;
    }
    return false;
}

T x = (f(x) ? x : T());

It is known to compile in the GCC versions this project uses (4.1.2 and 3.2.3... don't even get me started...), but should it? 众所周知,在这个项目使用的GCC版本中编译(4.1.2和3.2.3 ......甚至没有让我开始...),但应该吗?

Edit : I added some details, for example as to how f() conceptually looks like in the original code. 编辑 :我添加了一些细节,例如f()概念在原始代码中的样子。 Basically, it's meant to be initialize x in certain conditions. 基本上,它意味着在某些条件下初始化x

Syntactically it is, however if you try this 从语法上来说,如果你试试这个

#include <iostream>
using namespace std;

typedef int T;
bool f(T& x)
{
    return true;
}
int main()
{
    T x = (f(x) ? x : T());
    cout << x;
}

it outputs some random junk. 它输出一些随机垃圾。 However, if you modify 但是,如果你修改

bool f(T& x)
{
    x = 10;
    return true;
}

then it outputs 10. In the first case, the object x is declared, and the compiler assigns some pseudo-arbitrary value (so you do not initialize it), whereas in the second you specifically assign a value ( T() , ie 0 ) after the declaration, ie you initialize it. 然后它输出10.在第一种情况下,声明对象x ,并且编译器分配一些伪任意值(所以你不初始化它),而在第二种情况下你专门分配一个值( T() ,即0声明后,即你初始化它。

I think your question is similar to this one: Using newly declared variable in initialization (int x = x+1)? 我认为你的问题类似于这个问题: 在初始化中使用新声明的变量(int x = x + 1)?

It undoubtedly should compile, but may conditionally lead to undefined behavior. 它无疑应该编译,但可能有条件地导致未定义的行为。

  • If T is a non-primitive type, undefined behavior if it is assigned. 如果T是非基本类型,则指定未定义的行为。
  • If T is a primitive type, well-defined behavior if it is non-local, and undefined behavior if it is not assigned before reading (except for character types, where it is defined to give an unspecified value). 如果T是基本类型,则定义良好的行为(如果它是非本地的),如果未在读取之前分配,则定义为未定义的行为(除了字符类型,定义它以给出未指定的值)。

The relevant part of the Standard is this rule from 3.8, Object lifetime: 标准的相关部分是3.8规则,对象生命周期:

The lifetime of an object of type T begins when: 类型T对象的生命周期始于:

  • storage with the proper alignment and size for type T is obtained, and 获得具有适当对齐和T型尺寸的存储,并且
  • if the object has non-trivial initialization, its initialization is complete. 如果对象具有非平凡的初始化,则其初始化完成。

So the lifetime of x hasn't started yet. 所以x的生命周期还没有开始。 In the same section, we find the rule that governs using x : 在同一节中,我们找到了使用x控制的规则:

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 . 类似地, 在对象的生命周期开始之前但在对象将占用的存储已经被分配之后,或者在对象的生命周期结束之后并且在对象占用的存储被重用或释放之前, 任何引用的glvalue之前可以使用原始对象,但仅限于有限的方式 For an object under construction or destruction, see 12.7. 对于正在建造或销毁的物体,见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. 否则, 这样的glvalue指的是已分配的存储(3.7.4.2),并且使用不依赖于其值的glvalue的属性是明确定义的。 The program has undefined behavior if : 如果出现以下情况,该程

  • an lvalue-to-rvalue conversion (4.1) is applied to such a glvalue, 左值到右值的转换(4.1)应用于这样的glvalue,
  • the glvalue is used to access a non-static data member or call a non-static member function of the object , or glvalue用于访问非静态数据成员或调用对象的非静态成员函数 ,或
  • the glvalue is bound to a reference to a virtual base class (8.5.3), or glvalue绑定到对虚基类(8.5.3)的引用,或
  • the glvalue is used as the operand of a dynamic_cast (5.2.7) or as the operand of typeid. glvalue用作dynamic_cast(5.2.7)的操作数或typeid的操作数。

If your type is non-primitive, then trying to assign it is actually a call to T::operator= , a non-static member function. 如果你的类型是非原始的,那么尝试分配它实际上是对T::operator=的调用,这是一个非静态成员函数。 Full-stop, that is undefined behavior according to case 2. 完全停止,根据案例2,这是未定义的行为。

Primitive types are assigned without invoking a member function, so let's now take a closer look at section 4.1, Lvalue-to-rvalue conversion, to see when exactly that lvalue-to-rvalue conversion will be undefined behavior: 在不调用成员函数的情况下分配原始类型,现在让我们仔细看看4.1节,左值到右值的转换,以确定何时该左值到右值的转换将是未定义的行为:

When an lvalue-to-rvalue conversion occurs in an unevaluated operand or a subexpression thereof (Clause 5) the value contained in the referenced object is not accessed. 当在未评估的操作数或其子表达式中发生左值到右值转换时(第5条),不访问引用对象中包含的值。 In all other cases, the result of the conversion is determined according to the following rules: 在所有其他情况下,转换结果根据以下规则确定:

  • If T is (possibly cv-qualified ) std::nullptr_t , the result is a null pointer constant (4.10). 如果T是(可能是cv限定的std::nullptr_t ,则结果是空指针常量(4.10)。
  • Otherwise, if T has a class type, the conversion copy-initializes a temporary of type T from the glvalue and the result of the conversion is a prvalue for the temporary. 否则,如果T具有类类型,则转换复制 - 从glvalue初始化类型T的临时值,并且转换的结果是临时的prvalue。
  • Otherwise, if the object to which the glvalue refers contains an invalid pointer value (3.7.4.2, 3.7.4.3), the behavior is implementation-defined. 否则,如果glvalue引用的对象包含无效指针值(3.7.4.2,3.7.4.3),则行为是实现定义的。
  • Otherwise, if T is a (possibly cv-qualified ) unsigned character type (3.9.1), and the object to which the glvalue refers contains an indeterminate value (5.3.4, 8.5, 12.6.2), and that object does not have automatic storage duration or the glvalue was the operand of a unary & operator or it was bound to a reference, the result is an unspecified value. 否则,如果T是(可能是cv限定的 )无符号字符类型(3.9.1),并且glvalue引用的对象包含不确定的值(5.3.4,8.5,12.6.2),并且该对象不有自动存储持续时间或glvalue是一元&运算符的操作数或它绑定到引用,结果是一个未指定的值。
  • Otherwise, if the object to which the glvalue refers contains an indeterminate value, the behavior is undefined. 否则,如果glvalue引用的对象包含不确定的值,则行为未定义。
  • Otherwise, the value contained in the object indicated by the glvalue is the prvalue result. 否则,glvalue指示的对象中包含的值是prvalue结果。

(note that these rules reflect a rewrite for the upcoming C++14 standard in order to make them easier to understand, but I don't think there's an actual change in the behavior here) (请注意,这些规则反映了即将推出的C ++ 14标准的重写,以使它们更容易理解,但我认为这里的行为没有实际的变化)

Your variable x has 1 an indeterminate value at the time an lvalue-reference is made and passed to f() . 在进行左值引用并将其传递给f()变量x具有1个不确定值。 As long as that variable has primitive type and its value is assigned before it is read (a read is lvalue-to-rvalue conversion), the code is fine. 只要该变量具有基本类型并且在读取之前分配其值(读取是左值到右值的转换),代码就可以了。

If the variable isn't assigned before being read, the effect depends on T . 如果在读取之前未分配变量,则效果取决于T Character types will cause code that executes and uses an arbitrary but legal character value. 字符类型将导致代码执行并使用任意但合法的字符值。 All other types cause undefined behavior. 所有其他类型都会导致未定义的行为


1 Unless x has static storage duration, for example a global variable. 1除非x具有静态存储持续时间,例如全局变量。 In that case it is zero-initialized before execution, according to section 3.6.2 Initialization of non-local variables: 在这种情况下,根据第3.6.2节非局部变量的初始化,它在执行前是零初始化的:

Variables with static storage duration (3.7.1) or thread storage duration (3.7.2) shall be zero-initialized (8.5) before any other initialization takes place. 具有静态存储持续时间(3.7.1)或线程存储持续时间(3.7.2)的变量应在任何其他初始化发生之前进行零初始化(8.5)。

In this case of static storage duration it is not possible to run into lvalue-to-rvalue conversion of an unspecified value. 在这种静态存储持续时间的情况下,不可能遇到未指定值的左值到右值转换。 But zero-initialization is not a valid state for all types, so still be careful of that. 但是零初始化对于所有类型都不是有效状态,所以仍然要小心。

Although scope plays a role the real issue is about object lifetime and more exactly for object with non-trivial initialization when does the lifetime begin. 虽然范围起着重要作用,但真正的问题是关于对象的生命周期,更确切地说,对于具有非平凡初始化的对象,生命周期开始时。

This is closely related to Can initializing expression use the variable itself? 这与初始化表达式使用变量本身密切相关吗? and Is passing a C++ object into its own constructor legal? 并将C ++对象传递给自己的构造函数合法吗? . Although my answers to those questions do not neatly answer this question, so it does not seem like a duplicate. 虽然我对这些问题的回答并没有巧妙地回答这个问题,但它似乎并不重复。

The key portion of the draft C++ standard we are concerned with here is section 3.8 [basic.life] which says: 我们关注的C ++标准草案的关键部分是3.8[basic.life] ,它说:

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 获得具有适当对齐和T型尺寸的存储,并且
  • if the object has non-trivial initialization, its initialization is complete. 如果对象具有非平凡的初始化,则其初始化完成。

So in this case we satisfy the first bullet, storage has been obtained. 所以在这种情况下我们满足第一个子弹,已经获得了存储。

The second bullet is where we find trouble: 第二个子弹是我们遇到麻烦的地方:

  • do we have non-trivial initialization 我们有非平凡的初始化吗?
  • and if so is the initialization complete 如果是,则初始化完成

Non-trivial initialization case 非平凡的初始化案例

We can get a base reasoning from defect report 363 which asks: 我们可以从缺陷报告363中得到一个基本推理,它要求:

And if so, what is the semantics of the self-initialization of UDT? 如果是这样,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. 3.8 [basic.life]第6段表明此处的引用是有效的。 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 before the lifetime of an object begins we are limited in what we can do with an object. 因此,在对象的生命周期开始之前,我们在对象的使用方面受到限制。 We can see from the defect report binding a reference to x is valid as long as it binds directly. 我们可以从缺陷报告中看到绑定对x的引用是有效的,只要它直接绑定即可。

What we can do is covered in section 3.8 ( The same section and paragraph the defect report quotes ) says ( emphasis mine ): 我们可以做的事情在第3.8节( 缺陷报告引用的相同部分和段落 )中说明( 强调我的 ):

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. 类似地,在对象的生命周期开始之前但在对象将占用的存储已经被分配之后,或者在对象的生命周期结束之后并且在对象占用的存储被重用或释放之前,任何引用的glvalue之前可以使用原始对象,但仅限于有限的方式。 For an object under construction or destruction, see 12.7. 对于正在建造或销毁的物体,见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. 否则,这样的glvalue指的是已分配的存储(3.7.4.2),并且使用不依赖于其值的glvalue的属性是明确定义的。 The program has undefined behavior if : 如果出现以下情况,该程

  • an lvalue-to-rvalue conversion (4.1) is applied to such a glvalue, 左值到右值的转换(4.1)应用于这样的glvalue,

  • the glvalue is used to access a non-static data member or call a non-static member function of the object, or glvalue用于访问非静态数据成员或调用对象的非静态成员函数,或

  • the glvalue is bound to a reference to a virtual base class (8.5.3), or glvalue绑定到对虚基类(8.5.3)的引用,或

  • the glvalue is used as the operand of a dynamic_cast (5.2.7) or as the operand of typeid. glvalue用作dynamic_cast(5.2.7)的操作数或typeid的操作数。

In your case we are accessing a non-static data member here, see emphasis above: 在您的情况下,我们在这里访问非静态数据成员,请参阅上面的重点:

r = ...;

So if T has non-trivial initialization then this line invokes undefined behavior and so would reading from r which would also be an access, covered in defect report 1531 . 因此,如果T具有非平凡的初始化,则该行调用未定义的行为,因此从r读取也将是访问,在缺陷报告1531中涵盖。

If x has static storage duration it will be zero-initialized but as far as I can tell this does not count as it's initialization is complete since the constructor would be called during dynamic initialization. 如果x具有静态存储持续时间,则它将被初始化为零,但据我所知,由于在动态初始化期间将调用构造函数, 因此初始化已完成

Trivial Initialization case 琐碎的初始化案例

If T has trivial initializaton then the lifetime begins once storage is obtained and writing to r is well defined behavior. 如果T具有微不足道的初始化,则一旦获得存储,则生命周期开始,并且写入r是明确定义的行为。 Although note that reading r before it has initialized will invoke undefined behavior since it would produce an indeterminate value . 虽然注意在初始化之前读取r将调用未定义的行为, 因为它会产生不确定的值 If x has static storage duration then it is zero-initialized and we don't have this issue. 如果x具有静态存储持续时间,那么它是零初始化的,我们没有这个问题。

Should it compile, in either cases whether you are invoking undefined behavior or not this allowed to compile. 它是否应该编译,在任何一种情况下,无论你是否正在调用未定义的行为,这都允许编译。 The compiler is not obligated to produce a diagnostic for undefined behavior although it may. 尽管可能,编译器没有义务为未定义的行为生成诊断。 It is only obligated to produce a diagnostic for ill-formed code which none of the troublesome cases here are. 它只有义务对不良形式的代码进行诊断,这里没有任何麻烦的情况。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM