Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

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!

like image 838
Johannes Schaub - litb Avatar asked Apr 16 '11 20:04

Johannes Schaub - litb


People also ask

What is const T?

T const * const aConstant (or const T * const aConstant ) declares a constant pointer to a constant T .

What does const T& mean in C++?

const T& value() const {... } This means the function value() will return a const T& type and in between (in the function) won't modify the class itself.

Why const correctness?

The benefit of const correctness is that it prevents you from inadvertently modifying something you didn't expect would be modified.

Does const go before or after type?

The first thing to know is that when you declare any variable or object, the qualifier 'const' can be written before or after the type.


2 Answers

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

like image 52
6502 Avatar answered Oct 07 '22 02:10

6502


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.

like image 37
sehe Avatar answered Oct 07 '22 01:10

sehe