lifetime of objects (here: strings)

S

Stefan Ram

I still struggle with C++ memory management, so here are some
questions:

There seem to be three fundamental lifetimes of objects in C++?

- scope-based lifetime (auto variables)
- dynamic lifetime (»new«)
- temporaries (expression based?)

When »a« and »b« are instances of »::std::string«, what is the
lifetime of »a+b«?

A tutorial page, chosen at random,

http://www.cprogramming.com/tutorial/string.html

does not seem to mention or answer this question. Shouldn't
this be very important for every programmer to know from the
moment he starts to use objects?

When a function returns a instance of a class, is it the most
common case that it returns a temporary, so that this can be
assumed, unless the documentation tells something else?

Is it ok to return a temporary, as in the following example?

::std::string f()
{ ::std::string const a( "alpha" );
::std::string const b( "beta" );
return a + b; }

If this is a temporary, IIRC, it is guaranteed to exist during
the evaluation of the full-expression of the call site? So one
either has to pass it to another function there or copy it to
an object with longer life time.

The tutorial also uses:

string my_string3 = my_string1 + my_string2;

This seems to be »copy initialization«.

Is it of any advantage to use »direct initialization« in this
case? Which would be:

string my_string3( my_string1 + my_string2 );

Are the any guidelines, when to prefer copy initialization
and when to prefer direct initialization?

Ok, and I assume, what one can never to with a temporary
object, is to bind a reference name to it, as in:

string & my_string3( my_string1 + my_string2 );

But, this would be allowed with a const reference, because
for this special case ISO C++ extends the lifetime?

string const & my_string3( my_string1 + my_string2 );

Is this what »GotW #88« is about?

So for the same reason, a function with a reference parameter
can not be called with a temporary, unless the reference
parameter is const?
 
V

Victor Bazarov

Stefan said:
I still struggle with C++ memory management, so here are some
questions:

There seem to be three fundamental lifetimes of objects in C++?

- scope-based lifetime (auto variables)
- dynamic lifetime (»new«)
- temporaries (expression based?)

Actually if you count temporaries separate from 'scope-based', then
there is the fourth - static.
When »a« and »b« are instances of »::std::string«, what is the
lifetime of »a+b«?

It's a temporary.
A tutorial page, chosen at random,

http://www.cprogramming.com/tutorial/string.html

does not seem to mention or answer this question. Shouldn't
this be very important for every programmer to know from the
moment he starts to use objects?

When a function returns a instance of a class, is it the most
common case that it returns a temporary, so that this can be
assumed, unless the documentation tells something else?

Is it ok to return a temporary, as in the following example?

::std::string f()
{ ::std::string const a( "alpha" );
::std::string const b( "beta" );
return a + b; }

Yes, it is.
If this is a temporary, IIRC, it is guaranteed to exist during
the evaluation of the full-expression of the call site? So one
either has to pass it to another function there or copy it to
an object with longer life time.
Yes.


The tutorial also uses:

string my_string3 = my_string1 + my_string2;

This seems to be »copy initialization«.

It is.
Is it of any advantage to use »direct initialization« in this
case? Which would be:

string my_string3( my_string1 + my_string2 );

No, in this case they are equivalent.
Are the any guidelines, when to prefer copy initialization
and when to prefer direct initialization?

Always prefer direct initialisation.
Ok, and I assume, what one can never to with a temporary
object, is to bind a reference name to it, as in:

string & my_string3( my_string1 + my_string2 );

But, this would be allowed with a const reference, because
for this special case ISO C++ extends the lifetime?

string const & my_string3( my_string1 + my_string2 );
Correct.

Is this what »GotW #88« is about?

Probably. I didn't look.
So for the same reason, a function with a reference parameter
can not be called with a temporary, unless the reference
parameter is const?

Yes.

V
 
A

Alf P. Steinbach

* Victor Bazarov:
Always prefer direct initialisation.

Depends on your criteria.

I'd say "always prefer copy initialization".

For readability and clarity, plus it's a bit more general and for newbies avoids
the attempted-variable-declaration-that-turned-out-to-be-a-routine-declaration.


Cheers,

- Alf
 
S

Stefan Ram

Victor Bazarov said:

Thank you!

In the following block, all lines seems to be correct,
but should line 3 should be preferred to line 2,
because it avoids a copy operation and therefore is faster?

#include <string>

....

{ ::std::string const a( "alpha" ); /* line 0 */
::std::string const b( "beta" ); /* line 1 */
::std::string c( a + b ); /* line 2 */
::std::string const & d( a + b ); /* line 3 */

/* possible continuation of the block,
possibly using a, b, c, and d. */ }
 
V

Victor Bazarov

Stefan said:
Thank you!

In the following block, all lines seems to be correct,
but should line 3 should be preferred to line 2,
because it avoids a copy operation and therefore is faster?

#include <string>

...

{ ::std::string const a( "alpha" ); /* line 0 */
::std::string const b( "beta" ); /* line 1 */
::std::string c( a + b ); /* line 2 */
::std::string const & d( a + b ); /* line 3 */

/* possible continuation of the block,
possibly using a, b, c, and d. */ }

It's OK to use a ref to const if you don't care about re-using the
object or changing it. Keep in mind though, that it still makes the
code somewhat convoluted, and that is justified by what seems to be
premature optimisation. It's easier on a human brain to deal with 'c'
since it's an object (and there is an object behind 'd' as well), and
the copying of this kind is usually taken care of by the optimizing
compiler.

Optimize what you've measured.

V
 
J

James Kanze

I still struggle with C++ memory management, so here are some
questions:
There seem to be three fundamental lifetimes of objects in C++?
- scope-based lifetime (auto variables)
- dynamic lifetime (»new«)
- temporaries (expression based?)

There are more than that. There's static lifetime, of
course, and there's the lifetime of objects thrown as
exceptions, and there may be a few others that I've
forgotten. (IIRC, function arguments also have a special
lifetime.)
When »a« and »b« are instances of »::std::string«, what is the
lifetime of »a+b«?

It's a temporary, so the lifetime is until the end of the
full expression. With a couple of obvious exceptions, and
one not so obvious one, but at least until the end of the
full expression.
A tutorial page, chosen at random,

Don't choose your tutorials at random. There's a lot of
junk out there. (No comment on the link you give---I've not
seen it. But if you reall choose at random, you're more
likely to get junk than anything worthwhile.)
does not seem to mention or answer this question. Shouldn't
this be very important for every programmer to know from the
moment he starts to use objects?

It depends on the object. For objects like std::string, all
you really need to know is the minimum lifetime, and most of
the time, not even that.
When a function returns a instance of a class, is it the
most common case that it returns a temporary, so that
this can be assumed, unless the documentation tells
something else?

By definition, the return value of a function is a
temporary.
Is it ok to return a temporary, as in the following example?
::std::string f()
{ ::std::string const a( "alpha" );
::std::string const b( "beta" );
return a + b; }
Yes.

If this is a temporary, IIRC, it is guaranteed to exist
during the evaluation of the full-expression of the call
site? So one either has to pass it to another function
there or copy it to an object with longer life time.

There's (officially) a copy. Officially, because the
standard explicitly gives the compiler the right to
eliminate it, by merging the temporary a+b with the
temporary returned from the function. Most compilers
actually do this optimization.
The tutorial also uses:
string my_string3 = my_string1 + my_string2;
This seems to be »copy initialization«.
Formally.

Is it of any advantage to use »direct initialization« in this
case? Which would be:
string my_string3( my_string1 + my_string2 );

Since the type of string1+string2 is the same as the target
type, the two are effectively the same.
Are the any guidelines, when to prefer copy
initialization and when to prefer direct initialization?

Do what the house coding guidelines say.
Ok, and I assume, what one can never to with a temporary
object, is to bind a reference name to it, as in:
string & my_string3( my_string1 + my_string2 );
But, this would be allowed with a const reference,
because for this special case ISO C++ extends the
lifetime?

Exact. But the effect is not transitive; the lifetime is
only extended to match the reference initialized with the
temporary, not to other references which were initialized
from that reference.
string const & my_string3( my_string1 + my_string2 );
Is this what »GotW #88« is about?
Yes.

So for the same reason, a function with a reference
parameter can not be called with a temporary, unless the
reference parameter is const?

Yes, but that has nothing to do with lifetime. It's just a
rule. The original rule allowed binding a temporary to any
reference. People got into trouble with temporaries that
were the result of implicit conversions, e.g.:

void
incr( int& i )
{
++ i ;
}

void
f()
{
unsigned j = 42 ;
incr( j ) ; // does *NOT* change j.
}

So the rule was changed.
 
J

James Kanze

* Victor Bazarov:
Depends on your criteria.
I'd say "always prefer copy initialization".
For readability and clarity, plus it's a bit more general
and for newbies avoids the
attempted-variable-declaration-that-turned-out-to-be-a-routine-declaration.

The rule is simple: alway prefer copy initialization, to
avoid the most embarrassing parse you refer to, and always
prefer direct initialization, so that novice readers won't
confuse it with assignment.

You're damned if you do, and damned if you don't.

(Also, I can remember one very old compiler which didn't
accept direct initialization for basic types, pointers and
references.)
 
J

James Kanze

In the following block, all lines seems to be correct,
but should line 3 should be preferred to line 2,
because it avoids a copy operation and therefore is faster?
#include <string>

{ ::std::string const a( "alpha" ); /* line 0 */
::std::string const b( "beta" ); /* line 1 */
::std::string c( a + b ); /* line 2 */
::std::string const & d( a + b ); /* line 3 */

/* possible continuation of the block,
possibly using a, b, c, and d. */ }

No. Line 3 should be avoided, because it isn't idiomatic,
and because it depends on a less known and less obvious
feature. In general, std::string is designed to have value
semantics, so you use it like a value, i.e. like an int.
And don't worry about the rest. (The only time the rest
would be relevant is when you have to expose the internals,
e.g. using c_str() because you have to pass the pointer to a
legacy function.)
 
S

Stefan Ram

James Kanze said:
feature. In general, std::string is designed to have value
semantics, so you use it like a value, i.e. like an int.
And don't worry about the rest. (The only time the rest
would be relevant is when you have to expose the internals,
e.g. using c_str() because you have to pass the pointer to a
legacy function.)

Thanks!

So, unless »c_str()« (or »new« or »delete«) is used, there is
no means to obtain a reference to a ::std::string object, that
already has ceased to exist?

Could a malicious programmer somehow construct code that is
accepted by the compiler, does not use »new« or »delete«, and
still has either a memory leak or a use of or a reference to
an object that already has ceased to exist?

~~

Maybe using the property that the lifetime extension for
temporaries is not transitive?

::std::string const & c( a + b );

What do I have to do now with »c« to intentionally create a
bug where this lack of transitivity matters?
 
T

Thomas J. Gritzan

Stefan said:
Could a malicious programmer somehow construct code that is
accepted by the compiler, does not use »new« or »delete«, and
still has either a memory leak or a use of or a reference to
an object that already has ceased to exist?

int& badFunction()
{
int i;
return i;
}

This sometimes happens with local arrays used for c-style strings.
The variable is gone, but the function returns a reference or pointer
referring to it.
~~

Maybe using the property that the lifetime extension for
temporaries is not transitive?

::std::string const & c( a + b );

What do I have to do now with »c« to intentionally create a
bug where this lack of transitivity matters?

std::string const& bad =
std::max( std::string("bad"), std::string("code") );

Lifetime is extended only by direct initialization of a reference by a
temporary.

So the lifetime of a reference, that is bound only indirectly to a
temporary through other references, is not extended. Do you mean that by
transitivity?
 
S

Stefan Ram

Thomas J. Gritzan said:
Do you mean that by transitivity?

Thank you!

When I used that word, I referred to James Kanze's wording:

»But the effect is not transitive; the lifetime is only
extended to match the reference initialized with the
temporary, not to other references which were initialized
from that reference.«
 
S

Stefan Ram

James Kanze said:
Exact. But the effect is not transitive; the lifetime is
only extended to match the reference initialized with the
temporary, not to other references which were initialized
from that reference.

I am trying to construct an example where this intransitivity
can be seen. First attempt:

string const & a( "a" ); /* line 0 */
string const & b( "b" ); /* line 1 */
string const & c( a + b ); /* line 2 */
string const & d( c ); /* line 3 */
....

Line 0 and line 1 bind »a« and »b« to temporaries.
In line 2, »a + b« creates another temporary object, called »c«.
In line 3, the intransitivity seems to apply, but since the scope
of »d« is a part of the scope of »c«, the object »d« will live as
long as »c« lives, so »d« will also live until control exits
the block. So far, there should be no »dangling references«.

For the same reason, it should be okay to call a function
»void f( ::std::string const & x );« with »c«: Because the
lifetime of the incarnation of this function is a part of the
lifetime of the block containing the reference »c«.

One might try to create a dangling reference by »return d;«
in »::std::string const & f(){ /* code from above */ return d; }«.
This will indeed succeed (to create a dangling reference).
So is this the situation, where the intransitivity can be observed?

But the following also seems to return a dangling reference,
while the result is the first const reference the temporary is
bound to.

::std::string const & f(){ return "a"; }
 
J

James Kanze

I am trying to construct an example where this
intransitivity can be seen.

std::string const f( std::string const& arg )
{
return arg ;
}

void
g()
{
std::string const& s( f( std::string( "temp" ) ) ) ;
// ...
}
One might try to create a dangling reference by »return d;«
in »::std::string const & f(){ /* code from above */ return
d; }«. This will indeed succeed (to create a dangling
reference). So is this the situation, where the
intransitivity can be observed?

Two cases, really. (Maybe more, but two occur off hand.) As
above, using the temporary to initialize a reference to a
function which returns it, and using the reference in the
initialization of a dynamically allocated object.
But the following also seems to return a dangling reference,
while the result is the first const reference the temporary
is bound to.
::std::string const & f(){ return "a"; }

Yes. But this is more or less the classical "don't return a
reference or a pointer to a local variable or temporary. There
are two absolute limits to the lifetime of a temporary: the full
expression it is created in is the lower limit (unless one
considers function parameters temporaries), and the scope in
which the temporary is created is the upper one---all rules
extending the lifetime take second rang to this one.

A good example of this second rule coming into play:

class C { C() ; std::string const& rs ; } ;

C::C()
: rs( "abc" )
{
}

Clearly, rs is initialized with a temporary. Directly, so the
lifetime should be extended. Which it is, but only until the
end of the constructor. (The reason for this is simple: where
does the compiler put the temporary if its lifetime is to
outlive the call to the constructor?)

Finally: I would like to one again stress that std::string is a
value type. You should use it exactly like you would int or
double, at least with regards to whether to use a reference or
not. You don't generally have local variables or members which
are references to int or double, and you shouldn't generally
have local variables or members which are references to string.
(There are exceptions, of course, but these generally involve
references to objects which outlive the function.)
 
A

Alf P. Steinbach

* James Kanze:
std::string const f( std::string const& arg )
{
return arg ;
}

Typo, you meant

std::string const& f( std::string const& arg )

Yes?

void
g()
{
std::string const& s( f( std::string( "temp" ) ) ) ;
// ...
}

Cheers,

- Alf (typo-spotter)
 

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,769
Messages
2,569,580
Members
45,055
Latest member
SlimSparkKetoACVReview

Latest Threads

Top