473,769 Members | 2,134 Online
Bytes | Software Development & Data Engineering Community
+ Post

Home Posts Topics Members FAQ

Scope of const references to subojects of temporaries

In the following example, section #3 fails under VC98, VC2003, VC2005 Express
Beta (Aug 2004) and g++ 3.3.2. Is this just a pitfall of the C++
specification? Why don't any of the above compilers at least flag this as a
warning as they would when say trying to return a const & to a local?

In Section #2, the const B& Bref is initialized and bound to the temporary
returned from GetSettings(). That is the temporary B exists until Bref goes
out of scope.

What appears to be happening in section #3 is this:

1. A temporary B object is copy constructed and returned from the
GetSettings() call.
2. The GetData() call on that temporary returns a const & and no temporary
is created for its return, thus Cref2 is initialized to a reference a member
of a temporary object.
3. The temporary B object goes out of scope.
4. The Cref2.Test() call is then made on an object that has passed out of
scope and no longer exists.

When the const C& Cref2 is initialized to refer to a subobject of the
temporary B shouldn't that also cause the temporaries scope to be bound to
that of Cref2?

This behavior was discovered when we changed a rather large class
hierarchy's A::GetSettings( ) from returning by const & to be a return by
value instead and things quit working correctly. What we thought was a
couple line code re-factor turned out to have this nasty consequence. So I
ask, is the compiler correctly implementing the C++ spec here (or are all the
ones we tested broken)? And, can the compiler produce an error or warning to
alert the programmer?

Thanks

-------snip below here-------

#include <cassert>
#include <iostream>
#include <vector>

using namespace std;

struct C
{
C() : mBuffer(100, 0xDC) {cerr << "C()\n";}
C(const C &c) : mBuffer(c.mBuff er) {cerr << "C(const C &)\n";}
virtual ~C() {mBuffer.clear( ); cerr << "~C()\n";}

void Test() const {assert(!mBuffe r.empty());}

protected:
vector<char> mBuffer;
};
struct B
{
B() {cerr << "B()\n";}
B(const B &b) : mData(b.mData) {cerr << "B(const B &)\n";}
virtual ~B() {cerr << "~B()\n";}

const C &GetData() const {return mData;}

protected:
C mData;
};
struct A
{
A() {cerr << "A()\n";}
A(const A &a) {cerr << "A(const A &)\n";}
virtual ~A() {cerr << "~A()\n";}

virtual B GetSettings() const {return mSettings;}

protected:
B mSettings;
};
int main(void)
{
A anObject;

//1. This works
anObject.GetSet tings().GetData ().Test();

//2. This works as well
const B &Bref = anObject.GetSet tings();
const C &Cref = Bref.GetData();
Cref.Test();

/*
//3. This doesn't work...no compile warnings or errors, but assert pops
const C &Cref2 = anObject.GetSet tings().GetData ();
Cref2.Test();
*/

cerr << "End Scope of main()\n";
return 0;
}
Nov 17 '05 #1
10 1539
ATASLO wrote:
In the following example, section #3 fails under VC98, VC2003, VC2005 Express
Beta (Aug 2004) and g++ 3.3.2. Is this just a pitfall of the C++
specification?
Yes, you only get lifetime extension when a temporary is directly bound
to a reference.

Why don't any of the above compilers at least flag this as a warning as they would when say trying to return a const & to a local?
I think it's harder for the compiler to detect, at least in the general
case.
In Section #2, the const B& Bref is initialized and bound to the temporary
returned from GetSettings(). That is the temporary B exists until Bref goes
out of scope.

What appears to be happening in section #3 is this:

1. A temporary B object is copy constructed and returned from the
GetSettings() call.
2. The GetData() call on that temporary returns a const & and no temporary
is created for its return, thus Cref2 is initialized to a reference a member
of a temporary object.
3. The temporary B object goes out of scope.
4. The Cref2.Test() call is then made on an object that has passed out of
scope and no longer exists.

When the const C& Cref2 is initialized to refer to a subobject of the
temporary B shouldn't that also cause the temporaries scope to be bound to
that of Cref2?
Right, that's what's happening.

This behavior was discovered when we changed a rather large class
hierarchy's A::GetSettings( ) from returning by const & to be a return by
value instead and things quit working correctly. What we thought was a
couple line code re-factor turned out to have this nasty consequence. So I
ask, is the compiler correctly implementing the C++ spec here (or are all the
ones we tested broken)? And, can the compiler produce an error or warning to
alert the programmer?


Yes, and no. I don't know of any compiler that warns in this situation,
since it isn't a situation that can easily be detected at compile time.

Tom
Nov 17 '05 #2
"Tom Widmer" wrote:
ATASLO wrote:
In the following example, section #3 fails under VC98, VC2003, VC2005 Express
Beta (Aug 2004) and g++ 3.3.2. Is this just a pitfall of the C++
specification?


Yes, you only get lifetime extension when a temporary is directly bound
to a reference.


From section 12.2 of the C++ standard:
"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 or until the end of the scope in which the
temporary is created, whichever comes first."

Doesn't that imply that if the object to which the reference is being bound
is a subobject of a temporary itself, then the entire temporary complete
object's (ie parent's) scope is extended to that of the reference as well.

Nov 17 '05 #3
ATASLO wrote:
"Tom Widmer" wrote:
ATASLO wrote:
> In the following example, section #3 fails under VC98, VC2003, VC2005 Express
> Beta (Aug 2004) and g++ 3.3.2. Is this just a pitfall of the C++
> specification?


Yes, you only get lifetime extension when a temporary is directly bound
to a reference.


From section 12.2 of the C++ standard:
"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 or until the end of the scope in which the
temporary is created, whichever comes first."

Doesn't that imply that if the object to which the reference is being bound
is a subobject of a temporary itself, then the entire temporary complete
object's (ie parent's) scope is extended to that of the reference as well.


It doesn't just imply it; it directly states it. However, it doesn't apply
in your case, because you're not binding a temporary. You had:

/*
//3. This doesn't work...no compile warnings or errors, but assert pops
const C &Cref2 = anObject.GetSet tings().GetData ();
Cref2.Test();
*/

Now, B::GetData returns const C&, which is a reference, so you're binding a
reference. As for your other cases:

//1. This works
anObject.GetSet tings().GetData ().Test();

//2. This works as well
const B &Bref = anObject.GetSet tings();
const C &Cref = Bref.GetData();
Cref.Test();

Case (1) works because it's all one big expression, so any temporaries
produced live until the end of the full-expression.

Case (2) works because A::GetSettings returns a B, which you're binding to
Bref, so the lifetime rule applies. Then you can call B::GetData and use the
C& returned as long as Bref is still in scope, because it's keeping the B
(which contains the C to which Cref is bound) alive.

--
Doug Harrison
Microsoft MVP - Visual C++
Nov 17 '05 #4
"Doug Harrison [MVP]" wrote:
ATASLO wrote:
"Tom Widmer" wrote:
ATASLO wrote:
> In the following example, section #3 fails under VC98, VC2003, VC2005 Express
> Beta (Aug 2004) and g++ 3.3.2. Is this just a pitfall of the C++
> specification?

Yes, you only get lifetime extension when a temporary is directly bound
to a reference.


From section 12.2 of the C++ standard:
"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 or until the end of the scope in which the
temporary is created, whichever comes first."

Doesn't that imply that if the object to which the reference is being bound
is a subobject of a temporary itself, then the entire temporary complete
object's (ie parent's) scope is extended to that of the reference as well.


It doesn't just imply it; it directly states it. However, it doesn't apply
in your case, because you're not binding a temporary. You had:

/*
//3. This doesn't work...no compile warnings or errors, but assert pops
const C &Cref2 = anObject.GetSet tings().GetData ();
Cref2.Test();
*/


Since the C that is being returned by reference is a subobject of a
temporary, shouldn't it therefore be classified as a temporary as well?
Nov 17 '05 #5
ATASLO wrote:
"Doug Harrison [MVP]" wrote:

ATASLO wrote:

"Tom Widmer" wrote:
ATASLO wrote:

>In the following example, section #3 fails under VC98, VC2003, VC2005 Express
>Beta (Aug 2004) and g++ 3.3.2. Is this just a pitfall of the C++
>specificat ion?

Yes, you only get lifetime extension when a temporary is directly bound
to a reference.

From section 12.2 of the C++ standard:

"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 or until the end of the scope in which the
temporary is created, whichever comes first."

Doesn't that imply that if the object to which the reference is being bound
is a subobject of a temporary itself, then the entire temporary complete
object's (ie parent's) scope is extended to that of the reference as well.


It doesn't just imply it; it directly states it. However, it doesn't apply
in your case, because you're not binding a temporary. You had:

/*
//3. This doesn't work...no compile warnings or errors, but assert pops
const C &Cref2 = anObject.GetSet tings().GetData ();
Cref2.Test();
*/

Since the C that is being returned by reference is a subobject of a
temporary, shouldn't it therefore be classified as a temporary as well?


Subobjects relate to conversions from D to B& where const B& is what lives
on, and D is the type of the temporary. So, if in your example, C would
derive from Cbase publicly and you'd do

const Cbase & cb = Bref.GetData();

then, the actual C temporary would live on.

B is a temporary created during the evaluation of the expression. It only
lives until the full expression is evaluated. You could view it as the
argument to the operator. ("operator dot") function in that expression,
just before 'B::GetData()' is called.

At least, that's my take on it...

V
Nov 17 '05 #6
ATASLO wrote:
"Doug Harrison [MVP]" wrote:
It doesn't just imply it; it directly states it. However, it doesn't apply
in your case, because you're not binding a temporary. You had:

/*
//3. This doesn't work...no compile warnings or errors, but assert pops
const C &Cref2 = anObject.GetSet tings().GetData ();
Cref2.Test();
*/

Now, B::GetData returns const C&, which is a reference, so you're binding a
reference.


Since the C that is being returned by reference is a subobject of a
temporary, shouldn't it therefore be classified as a temporary as well?


But how's the compiler to know that? Make the definition of the function
GetData non-inline, and all it sees is a function that returns a const C&.
For all the compiler knows, that C& might refer to an unrelated object. The
validity of the code can't depend on things like this. By "subobject of a
temporary", the compiler means a base class or non-static member variable,
and "binding it to a reference" means binding it directly to that reference,
not through some function call.

--
Doug Harrison
Microsoft MVP - Visual C++
Nov 17 '05 #7
That's a fair point I hadn't considered. Changing the implementation of B to
the following solves the problem but isn't a very good solution:

struct B
{
B() : mData(C()) {cerr << "B()\n";}
B(const B &b) : mData(b.mData) {cerr << "B(const B &)\n";}
virtual ~B() {cerr << "~B()\n";}

const C &mData;
};
The above fails to compile on VC98, but does work on VC2003 and VC2005 Beta1.

Then the following code will work and not copy construct any tempory C
objects:

const C &Cref = anObject.GetSet tings().mData;
Cref.Test();

This solution violates the whole data encapsulation principal though in my
mind. The original problem though basically boils down to C++ not being able
to guarantee that an object obtained through a chained series of calls is
usable outside the scope of that chained expression. This still seems like a
hole in the language to me, but I can see how it may be difficult to build
all the various checks into the compiler.
"Doug Harrison [MVP]" wrote:
But how's the compiler to know that? Make the definition of the function
GetData non-inline, and all it sees is a function that returns a const C&.
For all the compiler knows, that C& might refer to an unrelated object. The
validity of the code can't depend on things like this. By "subobject of a
temporary", the compiler means a base class or non-static member variable,
and "binding it to a reference" means binding it directly to that reference,
not through some function call.

--
Doug Harrison
Microsoft MVP - Visual C++

Nov 17 '05 #8
ATASLO wrote:
That's a fair point I hadn't considered. Changing the implementation of B to
the following solves the problem but isn't a very good solution:

struct B
{
B() : mData(C()) {cerr << "B()\n";}
That's not safe, because the temporary C will be destroyed at the end of the
initialization of the reference mData.
B(const B &b) : mData(b.mData) {cerr << "B(const B &)\n";}
That may not be safe, either, because it's all too easy to bind a temporary
to a const reference. If the parameter b is a temporary, then mData is again
left to be a dangling reference, though here it lives a little longer than
above, throughout the initialization of the B and the full-expression it
appears in. My rule of thumb is to always make copies of const reference
parameters, or in this case, a subobject of the parameter.

Note that in a conformant compiler[*], you can't bind a temporary to a
non-const reference, so it may be acceptable for a class to keep a reference
to a non-const object. You should then comment the ctor with something like:

// Lifetime of x must exceed this object's lifetime.

This is usually enough to prevent accidents. As for the const reference
case, I'd recommend pass by value if possible, pointers (possibly reference
counted) if not.
[*] VC does allow the binding of temporaries to non-const references in many
cases for backward compatibility reasons. I haven't checked if Whidbey
closes this hole, but I hope it does.
virtual ~B() {cerr << "~B()\n";}

const C &mData;
};
The above fails to compile on VC98, but does work on VC2003 and VC2005 Beta1.
It may compile, but it'll blow up sooner or later as you begin to use the
dangling reference mData.
Then the following code will work and not copy construct any tempory C
objects:

const C &Cref = anObject.GetSet tings().mData;
Cref.Test();

This solution violates the whole data encapsulation principal though in my
mind. The original problem though basically boils down to C++ not being able
to guarantee that an object obtained through a chained series of calls is
usable outside the scope of that chained expression. This still seems like a
hole in the language to me, but I can see how it may be difficult to build
all the various checks into the compiler.


FWIW, I don't know how any language that strives for efficiency and doesn't
use garbage collection could avoid this issue.

--
Doug Harrison
Microsoft MVP - Visual C++
Nov 17 '05 #9
ATASLO wrote:
That's a fair point I hadn't considered. Changing the implementation of B to
the following solves the problem but isn't a very good solution:

struct B
{
B() : mData(C()) {cerr << "B()\n";}


That's covered in 12.2/5 - the temporary exists only until B's
constructor exits. After that, mData is a dangling reference, even if
the B object still exists.

Tom
Nov 17 '05 #10

This thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

1
1692
by: kaede | last post by:
Hi all, I would like to know if the following code is valid and not ill-formed. Data getData() { return Data(1, 2, 3); }
3
2284
by: kaede | last post by:
Hi all, Consider the following code fragment: // some data structure class Data { ... } // Container for the data structure Class Container {
19
2807
by: Christian Engström | last post by:
If you have a function that returns something by value, the gcc compiler (version 3.2.3 on Windows XP with MinGW) converts the returned value from the type you specify in the code, to the const version of that type. Is this a bug that is specific to gcc, or is it a flaw in the language specification that gcc diligently implements? For example, the below program produces the output Constant Mutable
39
3099
by: JKop | last post by:
Back when I read my first C++ book, I was given the following scenario: class Cheese { public: int number_of_holes; int colour;
5
1536
by: Dave | last post by:
Hello all, I've been wondering... Why is it that a reference may be bound only to a const object? If a reference were bound to a non-const object and that object were modified, what harm could result? A temporary is just as real of an object as any other. It lacks a name, but that doesn't make it less real. Class (no pun intended) warfare seems to be alive and well! Thanks, Dave
7
1692
by: REH | last post by:
Though unsafe, is this legal: const int& foo(const int& i) { return i; } int j = foo(5); does the temporary live long enough to be assigned to j?
14
6520
by: gerald.dalley | last post by:
I've been trying to pin down the scoping rules for temporary variables in C++. I know that doing something like: string s("abc"); const char *t = (s+"def").c_str(); cout << t; is invalid since (s+"def") creates a temporary which goes out of scope, thus leaving t a dangling pointer. What I'm wondering is whether temporaries go out of scope when their expression terminates or when
13
3985
by: dragoncoder | last post by:
Hi everyone, please consider the following function:- const int& foo ( const double& d ) { return d; } g++ compiles it with warnings and solaris CC gives error. I want to know if the code is correct according to the standard ?
5
2650
by: coolguyaroundyou | last post by:
Consider the following codes: class abc { int i; public: abc() : i(0) {} void func() { .....some code....} };
0
9589
marktang
by: marktang | last post by:
ONU (Optical Network Unit) is one of the key components for providing high-speed Internet services. Its primary function is to act as an endpoint device located at the user's premises. However, people are often confused as to whether an ONU can Work As a Router. In this blog post, we’ll explore What is ONU, What Is Router, ONU & Router’s main usage, and What is the difference between ONU and Router. Let’s take a closer look ! Part I. Meaning of...
0
9423
by: Hystou | last post by:
Most computers default to English, but sometimes we require a different language, especially when relocating. Forgot to request a specific language before your computer shipped? No problem! You can effortlessly switch the default language on Windows 10 without reinstalling. I'll walk you through it. First, let's disable language synchronization. With a Microsoft account, language settings sync across devices. To prevent any complications,...
0
10216
Oralloy
by: Oralloy | last post by:
Hello folks, I am unable to find appropriate documentation on the type promotion of bit-fields when using the generalised comparison operator "<=>". The problem is that using the GNU compilers, it seems that the internal comparison operator "<=>" tries to promote arguments from unsigned to signed. This is as boiled down as I can make it. Here is my compilation command: g++-12 -std=c++20 -Wnarrowing bit_field.cpp Here is the code in...
1
9997
by: Hystou | last post by:
Overview: Windows 11 and 10 have less user interface control over operating system update behaviour than previous versions of Windows. In Windows 11 and 10, there is no way to turn off the Windows Update option using the Control Panel or Settings app; it automatically checks for updates and installs any it finds, whether you like it or not. For most users, this new feature is actually very convenient. If you want to control the update process,...
0
9865
tracyyun
by: tracyyun | last post by:
Dear forum friends, With the development of smart home technology, a variety of wireless communication protocols have appeared on the market, such as Zigbee, Z-Wave, Wi-Fi, Bluetooth, etc. Each protocol has its own unique characteristics and advantages, but as a user who is planning to build a smart home system, I am a bit confused by the choice of these technologies. I'm particularly interested in Zigbee because I've heard it does some...
0
8873
agi2029
by: agi2029 | last post by:
Let's talk about the concept of autonomous AI software engineers and no-code agents. These AIs are designed to manage the entire lifecycle of a software development project—planning, coding, testing, and deployment—without human intervention. Imagine an AI that can take a project description, break it down, write the code, debug it, and then launch it, all on its own.... Now, this would greatly impact the work of software developers. The idea...
0
6675
by: conductexam | last post by:
I have .net C# application in which I am extracting data from word file and save it in database particularly. To store word all data as it is I am converting the whole word file firstly in HTML and then checking html paragraph one by one. At the time of converting from word file to html my equations which are in the word document file was convert into image. Globals.ThisAddIn.Application.ActiveDocument.Select();...
0
5310
by: TSSRALBI | last post by:
Hello I'm a network technician in training and I need your help. I am currently learning how to create and manage the different types of VPNs and I have a question about LAN-to-LAN VPNs. The last exercise I practiced was to create a LAN-to-LAN VPN between two Pfsense firewalls, by using IPSEC protocols. I succeeded, with both firewalls in the same network. But I'm wondering if it's possible to do the same thing, with 2 Pfsense firewalls...
3
2815
bsmnconsultancy
by: bsmnconsultancy | last post by:
In today's digital era, a well-designed website is crucial for businesses looking to succeed. Whether you're a small business owner or a large corporation in Toronto, having a strong online presence can significantly impact your brand's success. BSMN Consultancy, a leader in Website Development in Toronto offers valuable insights into creating effective websites that not only look great but also perform exceptionally well. In this comprehensive...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.