Passing reference to variable out of scope

J

Johannes Bauer

Hello Group,

please consider the following code

#include <vector>
#include <iostream>

#define USE_CONST
#define USE_STRING

class a {
public:
#ifdef USE_CONST
const std::string &moo;
a(const std::string &moo) : moo(moo) { };
#else
std::string &moo;
a(std::string &moo) : moo(moo) { };
#endif

void show() {
std::cerr << "String: '" << moo << "'" << std::endl;
};
};

int main() {
#ifdef USE_STRING
std::string str = std::string("Das ist ein Test!");
a abc = a(str);
abc.show();
#else
a xyz = a(std::string("Das ist ein Test!"));
xyz.show();
#endif

return 0;
}

There are four possible combinations of USE_CONST and USE_STRING:

1 USE_STRING && !USE_CONST => Fine
2 USE_STRING && USE_CONST => Fine
3 !USE_STRING && !USE_CONST => Compile Error
4 !USE_STRING && USE_CONST => Compiles, but behaves weird

So it is clear to me that when using number 3, the compiler throws an
error - since the created string object goes out of scope (and lifespan)
immediately after the constructor of "a" is called.

But what bewilders me a bit is that example #4 compiles cleanly without
any warning - yet there is no string output (it's just empty); probably
because it has already been destroyed when the implicit std::string's
destructor was called.

This seems intriguing. The string is already destroyed, however a may
keep a reference (moo) to it? How is that possible?

Greetings,
Johannes
 
M

Marco Manfredini

Johannes said:
So it is clear to me that when using number 3, the compiler throws an
error - since the created string object goes out of scope (and
lifespan) immediately after the constructor of "a" is called.

No, the compiler throws an error because you are trying to bind a const
reference to a non-const reference. This is completely unrelated to the
lifetime issue.
But what bewilders me a bit is that example #4 compiles cleanly
without any warning - yet there is no string output (it's just empty);
probably because it has already been destroyed when the implicit
std::string's destructor was called.

Jep. In your case, the lifetime of the string expression extends up to
the lifetime of the complete expression (the construction of xyz), but
not further.
This seems intriguing. The string is already destroyed, however a may
keep a reference (moo) to it? How is that possible?

References do not protect the referenced object from destruction - if an
objects dies before the reference to it vanishes, you end up with a
"dangling reference". In that respect, a reference isn't much better
that a pointer.

Btw. as a certain optimization and a feature to dumbfound beginners and
intermediate users, the following code:

const string &x="Hallo";
cout << x;

is correct.
 
J

Johannes Bauer

Marco said:
No, the compiler throws an error because you are trying to bind a const
reference to a non-const reference. This is completely unrelated to the
lifetime issue.

Oh, okay. Coming from C, I'm not used to a compiler being so strict on
the "const" keyword, but I like it.
References do not protect the referenced object from destruction - if an
objects dies before the reference to it vanishes, you end up with a
"dangling reference". In that respect, a reference isn't much better
that a pointer.

Okay, that sounds a little bit dangerous - as I thought C++ should
guarantee references are always valid (or at least always not-NULL).
Would at least valgrind detect access to a dangling reference?
Btw. as a certain optimization and a feature to dumbfound beginners and
intermediate users, the following code:

const string &x="Hallo";
cout << x;

Although I don't consider myself to be a greenhorn, this still
dumbfounds me. The assignment operator is overloaded for const char* to
invoke the constructor of std::string, meaning

const string &x = std::string("Hallo");

So - tell me, why doesn't the lifespan of the *actual* string end in
that same line, leaving x to be a dangling reference?

Greetings,
Johannes
 
C

coder

Oh, okay. Coming from C, I'm not used to a compiler being so strict on
the "const" keyword, but I like it.



Okay, that sounds a little bit dangerous - as I thought C++ should
guarantee references are always valid (or at least always not-NULL).
Would at least valgrind detect access to a dangling reference?



Although I don't consider myself to be a greenhorn, this still
dumbfounds me. The assignment operator is overloaded for const char* to
invoke the constructor of std::string, meaning

const string &x = std::string("Hallo");

So - tell me, why doesn't the lifespan of the *actual* string end in
that same line, leaving x to be a dangling reference?

I'm not entirely sure, but according to Stroustrup: "A temporary
created to hold a reference initializer persists until the end of its
reference's scope" (The C++ Programming Language, Pg 98). This is why
the lifespan doesn't end on the same line.

As for the first case, I'm not so sure, but I'll try to guess. You
were passing a temporary object to another function which was
receiving it in a reference parameter. Because the reference was not
visible in the original scope of the temporary, the temporary object
ceased to exist at the end of the expression.
 
M

Marco Manfredini

Johannes said:
const string &x = std::string("Hallo");

So - tell me, why doesn't the lifespan of the *actual* string end in
that same line, leaving x to be a dangling reference?

There is an explicit rule, that if a temporary is used to initialize a
const reference with *scoped* lifetime (ie. auto & global), then the
temporary remains active for the lifetime of the reference. That is the
compiler treats this (more or less) as:

const string __tmpval = std::string("Hallo");
const string &x = __tmpval;
 
J

Johannes Bauer

Marco said:
const string __tmpval = std::string("Hallo");
const string &x = __tmpval;

Alright, I get that.

What I'm not getting, however: consider the following:

#include <vector>
#include <iostream>

class a {
public:
const std::string &foo;
a(const std::string &bar) : foo(bar) { };

void show() {
std::cerr << "String: '" << foo << "'" << std::endl;
};
};

int main() {
a xyz = a(std::string("Das ist ein Test!"));
xyz.show();

return 0;
}

Here are four possible ways to declare foo/bar (&foo, foo, &bar, bar).
The problem is: it doesn't matter what the constructor declaration of
"bar" looks like, only the *implementation* knows if the object in
question is copied or referenced (i.e. it depends on the declaration of
"foo").

This means an "outsider" needs to know if a class will copy or reference
the given constructor parameters (i.e. the implementation), which is not
really that nice. It would be neat if that could be seen just by looking
at the prototype.

Greetings,
Johannes
 
I

Ioannis Gyftos

I'd like to clear something up on this point...

On case number 4, the output is ''. As I understand, on my machine,
technically, the compiler implements the reference as a pointer, and
since the object-referred-to is destroyed, that pointer is set to
NULL. So in the end, I have some equivalent of
std::cout << (char*)NULL;
Which prints nothing.

If this is indeed the way this behaves, is this undefined behavior?
(ie, what is expected of a "danging reference"?)
 
J

James Kanze

The same rules apply in C (except, of course, that C doesn't
have references). You can't assign a pointer to const to a
pointer to non-const in C.

Of course, that wasn't the problem in your original code.

Probably, at least if you tried to use it.

There's no assignment operator involved in the above. There is
an implicit conversion of the char const[6] to an std::string.

Because the standard says that it doesn't.
I'm not entirely sure, but according to Stroustrup: "A
temporary created to hold a reference initializer persists
until the end of its reference's scope" (The C++ Programming
Language, Pg 98). This is why the lifespan doesn't end on the
same line.
As for the first case, I'm not so sure, but I'll try to guess.
You were passing a temporary object to another function which
was receiving it in a reference parameter. Because the
reference was not visible in the original scope of the
temporary, the temporary object ceased to exist at the end of
the expression.

In the original code, there were two references: the argument to
the constructor, and the reference in the class. The first was
initialized by the temporary; if it had had a longer lifetime,
then the temporary's lifetime would have been extended. The
second was initialized by another reference, and so can have no
influence on any temporary, anywhere.

Another way of looking at it is to say that the extension of
lifetime is not transitive.

I might add, however, that reference members are a special case
anywhere, and have rules of their own when initialized with a
temporary. The simple rule is: don't.
 
M

Marco Manfredini

Johannes said:
Here are four possible ways to declare foo/bar (&foo, foo, &bar, bar).
The problem is: it doesn't matter what the constructor declaration of
"bar" looks like, only the *implementation* knows if the object in
question is copied or referenced (i.e. it depends on the declaration
of "foo").

This means an "outsider" needs to know if a class will copy or
reference the given constructor parameters (i.e. the implementation),
which is not really that nice. It would be neat if that could be seen
just by looking at the prototype.

Yes, it's always troublesome if ownership is not clearly defined and not
getting a hint from a function signature if it (possibly) escapes an
argument is sometimes unpleasant, because you have to rely on
documentation or (changing) internals.

The forthcoming standard introduces special "rvalue references" which,
if I'm informed right, could be used by a class (with a reference
member) to bounce "unsafe" rvalues.
 

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,776
Messages
2,569,603
Members
45,194
Latest member
KarriWhitt

Latest Threads

Top