Does “T const&t = C().a;” lengthen the lifetime of “a”?

Deadly 提交于 2020-01-01 23:55:33

问题


The following scenario is given, to be interpreted as C++0x code:

struct B { }; 
struct A { B b; }; 
int main() { 
  B const& b = A().b; 
  /* is the object still alive here? */
}

Clang and GCC (trunk version as of 2011/02) behave differently: Clang lengthens the lifetime. GCC moves B to a new temporary object, and then binds the reference to that new temporary.

I cannot find either behavior can be derived from the words of the Standard. The expression A().b is not a temporary (see 5.2.5). Can anyone please explain the following to me?

  • Desired behavior (the intent of the committee)
  • The behavior as you derive it from the FDIS

Thanks!


回答1:


In 12.2 paragraph 5 of N3126=10-0116 it's said that:

The second context [ in which temporaries are destroyed at a different point than the end of the full-expression ] is when a reference is bound to a temporary. The temporary to which the reference is bound or the temporary that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference except ...

and then follows a list of four special cases (ctor-inizializers, reference parameters, returned value, new initializer).

So (in this version) seems to me that clang is correct because you're binding the reference to a subobject of a temporary.

EDIT

Thinking to the base sub-object of an object this also seems to be the only reasonable behavior. The alternative would mean doing a slicing in:

Derived foo();
...
void bar()
{
    Base& x = foo(); // not very different from foo().b;
    ...
}

Actually after making a little experiment seems indeed that g++ differentiates between a member sub-object and a base sub-object, but I don't understand where this differentiation is made in the standard. The following is the test program I used and where it's clearly visible the different handling of the two cases... (B is Base, D is Derived and C is composed).

#include <iostream>

struct B
{
    B()
    { std::cout << "B{" << this << "}::B()\n"; }

    B(const B& x)
    { std::cout << "B{" << this << "}::B(const B& " << &x << ")\n"; }

    virtual ~B()
    { std::cout << "B{" << this << "}::~B()\n"; }

    virtual void doit() const
    { std::cout << "B{" << this << "}::doit()\n"; }
};

struct D : B
{
    D()
    { std::cout << "D{" << this << "}::D()\n"; }

    D(const D& x)
    { std::cout << "D{" << this << "}::D(const D& " << &x << ")\n"; }

    virtual ~D()
    { std::cout << "D{" << this << "}::~D()\n"; }

    virtual void doit() const
    { std::cout << "D{" << this << "}::doit()\n"; }
};

struct C
{
    B b;

    C()
    { std::cout << "C{" << this << "}::C()\n"; }

    C(const C& x)
    { std::cout << "C{" << this << "}::C(const C& " << &x << ")\n"; }

    ~C()
    { std::cout << "C{" << this << "}::~C()\n"; }
};

D foo()
{
    return D();
}

void bar()
{
    std::cout << "Before calling foo()\n";
    const B& b = foo();
    std::cout << "After calling foo()\n";
    b.doit();
    std::cout << "After calling b.doit()\n";

    const B& b2 = C().b;
    std::cout << "After binding to .b\n";
    b2.doit();
    std::cout << "After calling b2.doit()\n";
}

int main()
{
    std::cout << "Before calling bar()\n";
    bar();
    std::cout << "After calling bar()\n";
    return 0;
}

The output I get with g++ (Ubuntu/Linaro 4.4.4-14ubuntu5) 4.4.5 is

Before calling bar()
Before calling foo()
B{0xbf9f86ec}::B()
D{0xbf9f86ec}::D()
After calling foo()
D{0xbf9f86ec}::doit()
After calling b.doit()
B{0xbf9f86e8}::B()
C{0xbf9f86e8}::C()
B{0xbf9f86e4}::B(const B& 0xbf9f86e8)
C{0xbf9f86e8}::~C()
B{0xbf9f86e8}::~B()
After binding to .b
B{0xbf9f86e4}::doit()
After calling b2.doit()
B{0xbf9f86e4}::~B()
D{0xbf9f86ec}::~D()
B{0xbf9f86ec}::~B()
After calling bar()

In my opinion this is either a bug in g++ or a bug in what the c++ standard mandates if this is really the expected behavior or a possible acceptable behavior (but I must tell that I didn't really think about it a lot, this is just a feeling that something is wrong with this differentiation).




回答2:


Okay, I'm doing a 180 degrees on this

After refreshing my knowledge of the standard, I have to admit that it is probably right to expect the object referred to by b to remain alive (be extended) for the duration of scope in which the const& was initialized. I found GotW #88 a helpful source for this.

I fail to see how A().b is structurally or semantically different from

string f() { return "abc"; } // ABC initializes return-value **TEMP**

void g() {
const string& s = f();  // initializes with reference to a temp
  cout << s << endl;    // '*&s' is extended per standard
}

Sorry for any confusion I might have caused. I was a little out of my depth there.




回答3:


Temporary objects are distinguished by the circumstances of their creation. (§12.2 "Temporaries of class type are created in various contexts…")

For temporaries created by a reference declarator, §12.2 refers us to §8.5. C++03 and C++11 differ greatly in §8.5.3, but both clearly support your code.

C++03 says that either

— The reference is bound to the object represented by the rvalue (see 3.10) or to a sub-object within that object.

— A temporary of type “cv1 T2” [sic] is created, and a constructor is called to copy the entire rvalue object into the temporary. The reference is bound to the temporary or to a sub-object within the temporary.

The discussion is entirely in terms of subobjects, not distinguishing base classes from members. So, if binding a reference to a member is disallowed, then so is binding a member to a base, which rules out ScopeGuard.

C++11 is more verbose, but specifies

— Otherwise, the reference shall be an lvalue reference to a non-volatile const type (i.e., cv1 shall be const), or the reference shall be an rvalue reference. … If the initializer expression … is an xvalue, class prvalue, array prvalue or function lvalue and “cv1 T1” is reference- compatible with “cv2 T2” … then the reference is bound to the value of the initializer expression."

Combined with 6502's answer, and the pointlessness of binding a reference to a value which ends at the semicolon, it is apparent that C++11 continues to support this behavior.




回答4:


Let's see (all references are to the FDIS):

struct B { }; 
struct A { B b; }; 
int main() { 
  B const& b = A().b; 
}

1) 5.2.3/2 says A() is a prvalue.

2) 5.2.5/4 says that A().b is a prvalue because of point 1).

3) 8.5.3/5 says that B const& b binds directly to A().b without creating a temporary.

4) 12.2/5 says that the lifetime of a temporary bound to a reference is extended.

So it appears at least that GCC is wrong here.

Whether Clang is correct or if this is UB depends on whether the subobject of a temporary is itself a temporary. I'm quite sure the answer should be affirmative, but the Standard seems silent about the matter. Should someone submit a DR?

EDIT: As @6502 said, 3.7.5 indicates that the lifetime of a subobject is the lifetime of its complete object.



来源:https://stackoverflow.com/questions/5689463/does-t-constt-c-a-lengthen-the-lifetime-of-a

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!