reference lifetimes...

J

James

Here is my code:



#include <iostream>


struct foo
{
foo()
{
std::cout << this
<< "->foo::foo()"
<< std::endl;
}

~foo()
{
std::cout << this
<< "->foo::~foo()"
<< std::endl;
}
};


struct foo_holder
{
foo const& m_ref;
};


int main()
{
{
foo_holder fh = { foo() };
std::cout << "okay" << std::endl;
}

std::cout << std::endl;

{
foo const& ref = foo();
std::cout << "okay" << std::endl;
}

return 0;
}




Why does the const reference not properly maintain its lifetime over the
call to 'cout' when the reference is contained within a POD 'foo_holder'? I
get the following output:


0x22ff50->foo::foo()
0x22ff50->foo::~foo()
okay

0x22ff50->foo::foo()
okay
0x22ff50->foo::~foo()



Something seems terribly wrong here... Is there anyway to overcome this?
 
J

James

James Kanze said:
Here is my code: [...]
Why does the const reference not properly maintain its
lifetime over the call to 'cout' when the reference is
contained within a POD 'foo_holder'?

Compiler bug?
I get the following output:


Something seems terribly wrong here... Is there anyway to
overcome this?

Use a different compiler? I get

0012FF63->foo::foo()
okay
0012FF63->foo::~foo()

0012FF5B->foo::foo()
okay
0012FF5B->foo::~foo()

using VC++. (G++ behaves as you describe, however. This looks
like a bug in g++.)

What version of VC++ are you using? I just tried the code using the
following version:


Microsoft Visual Studio 2008
Version 9.0.30729.1 SP
Microsoft .NET Framework
Version 3.5 SP1
Installed Edition: VC Express


and I get the following output:


0012FE73->foo::foo()
0012FE73->foo::~foo()
okay

0012FF3F->foo::foo()
okay
0012FF3F->foo::~foo()



What the heck is going on!?





Please forgive my ignorance of the C++ standard, but:


Humm... Are you sure that lifetime of an is extended when assigned to a
constant reference residing in a POD using explicit initializer braces?


Can a POD legally include a constant reference to an object?
 
J

James

James said:
Please forgive my ignorance of the C++ standard, but:


Humm... Are you sure that lifetime of an is extended when assigned to a
constant reference residing in a POD using explicit initializer braces?

That should read as:

Humm... Are you sure that lifetime of a temporary _object_ is extended when
assigned to a constant reference residing in a POD using explicit
initializer braces?
 
J

James

Victor Bazarov said:
James wrote: [...]
Why does the const reference not properly maintain its lifetime over the
call to 'cout' when the reference is contained within a POD 'foo_holder'?
I get the following output:


0x22ff50->foo::foo()
0x22ff50->foo::~foo()
okay

0x22ff50->foo::foo()
okay
0x22ff50->foo::~foo()



Something seems terribly wrong here... Is there anyway to overcome this?

The form

T t = { blah };

is copy-initialisation. The compiler is free to create another temporary
(of your class 'foo_holder') before initializing 'fh' with it, which makes
the temporary's 'm_ref' member bound to the temporary 'foo'. The
'foo_holder' temporary is destroyed after initialising of the 'fh'
variable, causing the destruction of your foo' temporary.

The references when initialised with the above form are treated
differently. A possible copy of the object is created and the reference
is then bound to it. That temporary (either another one or the original
one) survives as long as the reference.

Also, I don't think this has anything to do with PODs.

I am a bit confused. So, are you saying that the output:


0x22ff50->foo::foo()
0x22ff50->foo::~foo()
okay

0x22ff50->foo::foo()
okay
0x22ff50->foo::~foo()



OR



0x22ff50->foo::foo()
okay
0x22ff50->foo::~foo()


0x22ff50->foo::foo()
okay
0x22ff50->foo::~foo()



is perfectly fine because of undefined behavior? James Kanze threw me off
when he said it could be compiler bug.


darn.
 
J

James Kanze

Here is my code:
#include <iostream>
struct foo
{
foo()
{
std::cout << this
<< "->foo::foo()"
<< std::endl;
}
~foo()
{
std::cout << this
<< "->foo::~foo()"
<< std::endl;
}
};
struct foo_holder
{
foo const& m_ref;
};
int main()
{
{
foo_holder fh = { foo() };
std::cout << "okay" << std::endl;
}
std::cout << std::endl;

{
foo const& ref = foo();
std::cout << "okay" << std::endl;
}

return 0;
}
Why does the const reference not properly maintain its
lifetime over the call to 'cout' when the reference is
contained within a POD 'foo_holder'?

Compiler bug?
I get the following output:


Something seems terribly wrong here... Is there anyway to
overcome this?

Use a different compiler? I get

0012FF63->foo::foo()
okay
0012FF63->foo::~foo()

0012FF5B->foo::foo()
okay
0012FF5B->foo::~foo()

using VC++. (G++ behaves as you describe, however. This looks
like a bug in g++.)
 
V

Victor Bazarov

James said:
Here is my code:



#include <iostream>


struct foo
{
foo()
{
std::cout << this
<< "->foo::foo()"
<< std::endl;
}

~foo()
{
std::cout << this
<< "->foo::~foo()"
<< std::endl;
}
};


struct foo_holder
{
foo const& m_ref;
};


int main()
{
{
foo_holder fh = { foo() };
std::cout << "okay" << std::endl;
}

std::cout << std::endl;

{
foo const& ref = foo();
std::cout << "okay" << std::endl;
}

return 0;
}




Why does the const reference not properly maintain its lifetime over the
call to 'cout' when the reference is contained within a POD
'foo_holder'? I get the following output:


0x22ff50->foo::foo()
0x22ff50->foo::~foo()
okay

0x22ff50->foo::foo()
okay
0x22ff50->foo::~foo()



Something seems terribly wrong here... Is there anyway to overcome this?

The form

T t = { blah };

is copy-initialisation. The compiler is free to create another
temporary (of your class 'foo_holder') before initializing 'fh' with it,
which makes the temporary's 'm_ref' member bound to the temporary 'foo'.
The 'foo_holder' temporary is destroyed after initialising of the 'fh'
variable, causing the destruction of your foo' temporary.

The references when initialised with the above form are treated
differently. A possible copy of the object is created and the reference
is then bound to it. That temporary (either another one or the original
one) survives as long as the reference.

Also, I don't think this has anything to do with PODs.

V
 
V

Victor Bazarov

James said:
Victor Bazarov said:
James wrote: [...]
Why does the const reference not properly maintain its lifetime over
the call to 'cout' when the reference is contained within a POD
'foo_holder'? I get the following output:


0x22ff50->foo::foo()
0x22ff50->foo::~foo()
okay

0x22ff50->foo::foo()
okay
0x22ff50->foo::~foo()



Something seems terribly wrong here... Is there anyway to overcome this?

The form

T t = { blah };

is copy-initialisation. The compiler is free to create another
temporary (of your class 'foo_holder') before initializing 'fh' with
it, which makes the temporary's 'm_ref' member bound to the temporary
'foo'. The 'foo_holder' temporary is destroyed after initialising of
the 'fh' variable, causing the destruction of your foo' temporary.

The references when initialised with the above form are treated
differently. A possible copy of the object is created and the
reference is then bound to it. That temporary (either another one or
the original one) survives as long as the reference.

Also, I don't think this has anything to do with PODs.

I am a bit confused. So, are you saying that the output:


0x22ff50->foo::foo()
0x22ff50->foo::~foo()
okay

0x22ff50->foo::foo()
okay
0x22ff50->foo::~foo()



OR



0x22ff50->foo::foo()
okay
0x22ff50->foo::~foo()


0x22ff50->foo::foo()
okay
0x22ff50->foo::~foo()



is perfectly fine because of undefined behavior? James Kanze threw me
off when he said it could be compiler bug.


darn.

Yep. Essentially it's the same as

class foo { ... }; // just like yours

class HasARef {
const foo& m_ref;
public:
HasARef(const foo& r) : m_ref(r) {}
};

#include <iostream>
int main() {
HasARef h(( foo() )); // need double parens
std::cout << "okay\n";
}

What happens here is the reference 'm_ref' is *not* bound directly to
the temporary, but instead goes through a "copying" process - the
argument to the constructor. *The argument* is bound to the temporary.
The 'm_ref' member isn't because there is no direct initialisation of
it with the expression that yields the temporary.

It's not a compiler bug, AFAICT.

V
 
J

James Kanze

Here is my code: [...]
Why does the const reference not properly maintain its
lifetime over the call to 'cout' when the reference is
contained within a POD 'foo_holder'?
Compiler bug?
I get the following output:
0x22ff50->foo::foo()
0x22ff50->foo::~foo()
okay
0x22ff50->foo::foo()
okay
0x22ff50->foo::~foo()
Something seems terribly wrong here... Is there anyway to
overcome this?
Use a different compiler? I get
0012FF63->foo::foo()
okay
0012FF63->foo::~foo()
0012FF5B->foo::foo()
okay
0012FF5B->foo::~foo()
using VC++. (G++ behaves as you describe, however. This
looks like a bug in g++.)
What version of VC++ are you using?
8.

I just tried the code using the following version:
Microsoft Visual Studio 2008
Version 9.0.30729.1 SP
Microsoft .NET Framework
Version 3.5 SP1
Installed Edition: VC Express
and I get the following output:


What the heck is going on!?

It sounds like they've changed it.
Please forgive my ignorance of the C++ standard, but:
Humm... Are you sure that lifetime of an is extended when
assigned to a constant reference residing in a POD using
explicit initializer braces?

POD has little to do with it. You're using aggregate
initialization. foo_holder is an aggregate, so each element is
initialized by the corresponding initialization expression,
using copy initialization. In other words, your two cases must
have exactly the same behavior, according to the standard.

(I'm curious, however. Since the behavior has changed in VC++9,
they've obviously changed it. Why? Did they accidentally
introduce a new bug, which curiously mimics exactly the behavior
of g++, or is there something I'm overlooking?)
Can a POD legally include a constant reference to an object?

No, but it doesn't matter. The key here is that you're using
aggregate initialization, so you're not initializing the
foo_holder, you're initializating each of its members.
 
J

James Kanze

T t = { blah };
is copy-initialisation.

But not of T. In his case, he copy initializes the member m_ref
of foo_holder with a temporary.
The compiler is free to create another temporary (of your
class 'foo_holder') before initializing 'fh' with it, which
makes the temporary's 'm_ref' member bound to the temporary
'foo'.

I don't see anything in the standard which says that. He's
using aggregate initialization, which means that he's providing
a list of expressions to initialize each of the elements of the
aggregate. In his case, foo() is the expression which
initializes the foo_holder::m_ref, and not the foo_holder
itself. And the distinction between copy initialization and
direct initialization doesn't apply to either references or
aggregate initialization. (If you'll look at the points in
§8.5/14, you'll see that the standard sends you to another
section for both aggregate initialization and reference
initialization, before considering whether it is copy
initialization or not.) The compiler can still make a copy (at
least at present), but then it is the copy which is bound to the
reference, and whose lifetime is extended. (And of course, if
the compiler did make a copy, we'd see two calls to the
destructor each time.)
The 'foo_holder' temporary is destroyed after initialising of
the 'fh' variable, causing the destruction of your foo'
temporary.

There is no foo_holder temporary.
The references when initialised with the above form are
treated differently. A possible copy of the object is created
and the reference is then bound to it. That temporary (either
another one or the original one) survives as long as the
reference.
Also, I don't think this has anything to do with PODs.

No. It's a question of aggregate initialization.
 
J

James Kanze

James said:
Victor Bazarov said:
James wrote: [...]
Why does the const reference not properly maintain its lifetime over
the call to 'cout' when the reference is contained within a POD
'foo_holder'? I get the following output:
0x22ff50->foo::foo()
0x22ff50->foo::~foo()
okay
0x22ff50->foo::foo()
okay
0x22ff50->foo::~foo()
Something seems terribly wrong here... Is there anyway to
overcome this?
The form
T t = { blah };
is copy-initialisation. The compiler is free to create
another temporary (of your class 'foo_holder') before
initializing 'fh' with it, which makes the temporary's
'm_ref' member bound to the temporary 'foo'. The
'foo_holder' temporary is destroyed after initialising of
the 'fh' variable, causing the destruction of your foo'
temporary.
The references when initialised with the above form are
treated differently. A possible copy of the object is
created and the reference is then bound to it. That
temporary (either another one or the original one) survives
as long as the reference.
Also, I don't think this has anything to do with PODs.
I am a bit confused. So, are you saying that the output:
0x22ff50->foo::foo()
0x22ff50->foo::~foo()
okay
0x22ff50->foo::foo()
okay
0x22ff50->foo::~foo()
OR
0x22ff50->foo::foo()
okay
0x22ff50->foo::~foo()
0x22ff50->foo::foo()
okay
0x22ff50->foo::~foo()
is perfectly fine because of undefined behavior? James Kanze
threw me off when he said it could be compiler bug.

I think it is a compiler bug. At least, I can't see any way of
reading the standard otherwise. (Victor is, I think, confusing
two separate issues.)
Yep. Essentially it's the same as
class foo { ... }; // just like yours
class HasARef {
const foo& m_ref;
public:
HasARef(const foo& r) : m_ref(r) {}
};
#include <iostream>
int main() {
HasARef h(( foo() )); // need double parens
std::cout << "okay\n";
}

Not at all. There's no aggregate initialization involved there.
Note that as he'd written it,
foo_holder h( (foo()) );
will not compile. You cannot construct a foo_holder from a foo
(temporary or not). You can't default construct it, because it
contains a reference. All you can do is copy construct it
(which wouldn't prolongue the lifetime of the temporary), and
aggregate initialize it, in which case, you actually initialize
each of its members---and initializing a reference directly with
a temporary extends the lifetime of that temporary.
What happens here is the reference 'm_ref' is *not* bound
directly to the temporary, but instead goes through a
"copying" process - the argument to the constructor.

Here, yes. But not in his example. Aggregate initialization is
not the same as a constructor.
*The argument* is bound to the temporary. The 'm_ref' member
isn't because there is no direct initialisation of it with the
expression that yields the temporary.
It's not a compiler bug, AFAICT.

You'll have to cite something from the standard to back that up.
In §8.5.1, the sandard says that the brace-enclosed
comma-separated list is a list of initializer-clauses for the
members of the aggregate; that certainly sounds like each member
is initialized with the expression. (This may have changed in
C++0x, since there's been a significant effort in unifying the
initializer syntax and semantics.)
 
V

Victor Bazarov

James said:
But not of T. In his case, he copy initializes the member m_ref
of foo_holder with a temporary.

I think you're contradicting yourself here. If 'T' is an aggregate, and
the form

T t = { <initialiser list> };

does not perform copy-initialisation (from some temporary aggregate into
the 't' object), then there is no copy-initialisation of member 'm_ref'
anywhere, it's direct initialisation of the reference with a temporary,
which we know as "binding to a temporary".
I don't see anything in the standard which says that. He's
using aggregate initialization, which means that he's providing
a list of expressions to initialize each of the elements of the
aggregate. In his case, foo() is the expression which
initializes the foo_holder::m_ref, and not the foo_holder
itself. And the distinction between copy initialization and
direct initialization doesn't apply to either references or
aggregate initialization. (If you'll look at the points in
§8.5/14, you'll see that the standard sends you to another
section for both aggregate initialization and reference
initialization, before considering whether it is copy
initialization or not.) The compiler can still make a copy (at
least at present), but then it is the copy which is bound to the
reference, and whose lifetime is extended. (And of course, if
the compiler did make a copy, we'd see two calls to the
destructor each time.)

Not a copy of 'foo', a copy of 'foo_holder', which has no destructor,
but it's irrelevant. It's possible that GC++ creators followed the same
faulty logic I did, and came to the conclusion that they need to create
a temporary of type 'foo_holder', which then allowed destruction of the
temporary 'foo'.
There is no foo_holder temporary.

Well, how do you know that the compiler doesn't create it? Unless the
OP looks at the generated assembly code, there is no way to tell, is
there? I mean, in real life. Perhaps you're saying that the Standard
requires that there shan't be any copies of 'foo_holder' objects created
during initialisation (brace-enclosed), IOW copying is prohibited. Then
you're right and the GC++ has a bug.

V
 
J

Joshua Maurice

You cannot construct a foo_holder from a foo
(temporary or not).  You can't default construct it, because it
contains a reference.  All you can do is copy construct it
(which wouldn't prolongue the lifetime of the temporary), and
aggregate initialize it, in which case, you actually initialize
each of its members---and initializing a reference directly with
a temporary extends the lifetime of that temporary.

and initializing a reference directly with
a temporary extends the lifetime of that temporary.

Quoting the C++03 standard, 12.2 "Temporary Objects" / 5
The second context 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 to a subobject of
which the temporary is bound persists
for the lifetime of the reference except as specified below. A
temporary bound to a reference member in a
constructor’s ctor-initializer (12.6.2) persists until the constructor
exits. A temporary bound to a reference
parameter in a function call (5.2.2) persists until the completion of
the full expression containing the call.
A temporary bound to the returned value in a function return statement
(6.6.3) persists until the function
exits. [...]

I can't find anything in 8.5.1 "Aggregates" which contradicts this. It
seems that the standard as written requires that temporaries bound to
member sub-object reference objects in aggregates initialized with
brace initialization should be extended to the lifetime of the
reference sub-object. This is an odd situation, as this is disallowed
for most reference sub-objects with the clause "A temporary bound to a
reference member in a constructor’s ctor-initializer (12.6.2) persists
until the constructor exits". It is my humble guess that this may be
an oversight in the standard, and that the writers intended for the
"constructor ctor-initializer" clause to catch all cases of reference
sub-objects.

Alternatively, it's an oversight in many implementations, as they
forgot that you can initialize a reference sub-object in ways other
than a constructor's ctor-initializer, namely brace initialization.
 
J

James Kanze

James Kanze wrote: [...]
But not of T. In his case, he copy initializes the member m_ref
of foo_holder with a temporary.
I think you're contradicting yourself here. If 'T' is an
aggregate, and the form
T t = { <initialiser list> };
does not perform copy-initialisation (from some temporary
aggregate into the 't' object), then there is no
copy-initialisation of member 'm_ref' anywhere, it's direct
initialisation of the reference with a temporary, which we
know as "binding to a temporary".

According to §8.5/12:

The initialization that occurs in argument passing,
function return, throwing an exception (15.1), handling
an exception (15.3), AND BRACE-ENCLOSED INITIALIZER
LISTS (8.5.1) is called copy-initialization and is
equivalent to the form

T x = a;

In other words, it's copy initialization, because the standard
says it's copy initialization. (Don't ask me why the standard
makes this choice. In most cases where the syntax requires one,
and only one, initializer, it's copy initialization, but this
isn't an absolute: static_cast requires one, and only one,
initializer, but is direct initialization.)
Not a copy of 'foo', a copy of 'foo_holder',

That's not at all what the standard says. The standard quite
clearly treats aggregate initialization as a separate case (in a
separate section), and talks of initializing each of the
elements. It's the same as if you had an array.
which has no destructor, but it's irrelevant. It's possible
that GC++ creators followed the same faulty logic I did, and
came to the conclusion that they need to create a temporary of
type 'foo_holder', which then allowed destruction of the
temporary 'foo'.

Or that they have some other bug.
Well, how do you know that the compiler doesn't create it?

The standard doesn't allow it.
Unless the OP looks at the generated assembly code, there is
no way to tell, is there? I mean, in real life. Perhaps
you're saying that the Standard requires that there shan't be
any copies of 'foo_holder' objects created during
initialisation (brace-enclosed), IOW copying is prohibited.
Then you're right and the GC++ has a bug.

You do raise an interesting point. What is g++ doing exactly.
I defined a second class, bar, exactly identical to foo (and
added tracing of the copy constructor to both as well), and
changed foo_holder to:

struct foo_holder { foo const& m_ref; bar m_val; };

I then get the following output from g++:

0x22ff40->foo::foo()
0x22ff54->bar::bar()
0x22ff40->foo::~foo()
okay
0x22ff54->bar::~bar()

0x22ff40->foo::foo()
okay
0x22ff40->foo::~foo()

Very inconsistent, if you ask me.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,965
Messages
2,570,148
Members
46,710
Latest member
FredricRen

Latest Threads

Top