问题
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