How to detect const reference to temporary issues at compile or runtime?

C

Clinton Mead

Hi All

I've found recently that most of the errors in my C++ programs are of
a form like the following example:

#include <iostream>

class Z
{
public:
Z(int n) : n(n) {}
int n;
};

class Y
{
public:
Y(const Z& z) : z(z) {}
const Z& z;
};

class X
{
public:
X(const Y& y) : y(y) {}
Y y;
};

class Big
{
public:
Big()
{
for (int i = 0; i < 1000; ++i) { a = i + 1000; }
}
int a[1000];
};

X get_x() { return X(Y(Z(123))); }

int main()
{
X x = get_x();
Big b;
std::cout << x.y.z.n << std::endl;
}

OUTPUT: 1000

I would expect this program to output 123 (the value of x.y.z.n set in
get_x()) but the creation of "Big b" overwrites the temporary Z. As a
result, the reference to the temporary Z in the object Y is now
overwritten with Big b, and hence the output is not what I would
expect.

When I compiled this program with gcc 4.5 with the option "-Wall", it
gave no warning.

The fix is obviously to remove the reference from the member Z in the
class Y. However, often class Y is part of a library which I have not
developed (boost::fusion most recently), and in addition the situation
is much more complicated than this example I've given.

This there some sort of option to gcc, or any additional software that
would allow me to detect such issues preferably at compile time, but
even runtime would be better than nothing?

Thanks,

Clinton
 
C

Clinton Mead

{ This article effectively replaces the previous one; in such a case,
please request cancellation of the previous one and submit a new one.

Follow-ups to this article (instead of the previous one) will be
cross-posted to comp.lang.c++ and comp.lang.c++.moderated. -mod }

Crossposting to comp.lang.c++.moderated with this reply.

Hi All

I've found recently that most of the errors in my C++ programs are of
a form like the following example:

#include <iostream>

class Z
{
public:
Z(int n) : n(n) {}
int n;

};

class Y
{
public:
Y(const Z& z) : z(z) {}
const Z& z;

};

class X
{
public:
X(const Y& y) : y(y) {}
Y y;

};

class Big
{
public:
Big()
{
for (int i = 0; i < 1000; ++i) { a = i + 1000; }
}
int a[1000];

};

X get_x() { return X(Y(Z(123))); }

int main()
{
X x = get_x();
Big b;
std::cout << x.y.z.n << std::endl;

}

OUTPUT: 1000

I would expect this program to output 123 (the value of x.y.z.n set in
get_x()) but the creation of "Big b" overwrites the temporary Z. As a
result, the reference to the temporary Z in the object Y is now
overwritten with Big b, and hence the output is not what I would
expect.

When I compiled this program with gcc 4.5 with the option "-Wall", it
gave no warning.

The fix is obviously to remove the reference from the member Z in the
class Y. However, often class Y is part of a library which I have not
developed (boost::fusion most recently), and in addition the situation
is much more complicated than this example I've given.

This there some sort of option to gcc, or any additional software that
would allow me to detect such issues preferably at compile time, but
even runtime would be better than nothing?

Thanks,

Clinton
 
R

Rolf Magnus

Clinton said:
This there some sort of option to gcc, or any additional software that
would allow me to detect such issues preferably at compile time, but
even runtime would be better than nothing?

You could try running the program in valgrind if you are using one of the
supported platforms. When I do that on my system, I get:

==12011== Invalid read of size 4
==12011== at 0x80486DF: main (mead.cpp:40)

Not a perfect description, but better than nothing.
 
S

SG

I've found recently that most of the errors in my C++ programs are
of a form like the following example:
[...]

class Y
{
 public:
 Y(const Z& z) : z(z) {}
 const Z& z;
};

[...reference to vanishing temporary...]

If you really need Y to store a reference to a Z object you can avoid
accidentally binding a reference to a temporary by makeing this
constructor *explicit* and taking a pointer parameter instead of a
reference. This forces you to use the address operator whenever you
try to invoke Y's constructor. (a) The address operator won't work on
temporary objects (unless you enabled some compiler extension perhaps)
and (b) The use of & should remind you to think about object life-
times.

This reminds me of one of the upcoming new standard library
facilities: reference_wrapper. If I remember correctly the
reference_wrapper has the following constructor overloads:

template<class T>
class reference_wrapper
{
public:
...
reference_wrapper(T&);
reference_wrapper(T&&) = delete; // #1
...
};

where #1 explicitly forbids initializing a reference_wrapper with an
rvalue expression (includes expressions yielding temporary objects).

Cheers!
SG
 
J

James Kanze

I've found recently that most of the errors in my C++ programs are of
a form like the following example:
#include<iostream>
class Z
{
public:
Z(int n) : n(n) {}
int n;
};
class Y
{
public:
Y(const Z& z) : z(z) {}
const Z& z;
};
class X
{
public:
X(const Y& y) : y(y) {}
Y y;
};
class Big
{
public:
Big()
{
for (int i = 0; i< 1000; ++i) { a = i + 1000; }
}
int a[1000];
};
X get_x() { return X(Y(Z(123))); }
int main()
{
X x = get_x();
Big b;
std::cout<< x.y.z.n<< std::endl;
}
OUTPUT: 1000
I would expect this program to output 123 (the value of x.y.z.n set in
get_x()) but the creation of "Big b" overwrites the temporary Z. As a
result, the reference to the temporary Z in the object Y is now
overwritten with Big b, and hence the output is not what I would
expect.

Simply put, your program has undefined behavior. You initialize the
data member of your 'Y' with a reference to a temporary. For a very
brief moment that reference is valid, then the temporary gets destroyed
and the reference becomes invalid. Any attempt to use it has undefined
behavior as the result.

I think he knows that. He's just wondering why compilers don't
warn in this simple case.
The compilers aren't *that* sophisticated. I don't know of any that
would exist that could determine the problem.

Using a reference parameter to initialize a reference member in
a constructor shouldn't be that hard to detect. The question is
whether it would result in too many false warnings: if your
"constract" says that the object passed in must live until the
end of the lifetime of the object being constructed, there's no
problem. Whether that's a frequent case or not in general,
I don't know. (It never occurs in my own code, because my
personal coding guidelines insist on using a pointer when an
argument must live beyond the end of the function: a pointer
means you need an lvalue, so trying to pass a temporary causes
a compiler error.)
 

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

Forum statistics

Threads
473,969
Messages
2,570,161
Members
46,705
Latest member
Stefkari24

Latest Threads

Top