C++ compiler "return" behavior (guru question ;)

A

Axel Bock

Hi all,

I am trying to get my head around what happens if I return a class
object from a function.
It seems C++ (MinGW) does not invoke the copy constructor if I do
something like this:

SomeObj foo()
{
SomeObj X;
// ....
return X;
}

BUT it does create a new object every time the function is invoked, and
it does seem like these objects are usable (I created a little test
program checking this :).
So basically I can go in main:

int main()
{
// ....
SomeObj bar = foo();
SomeObj bar2 = foo();
// for some strange reason the next thing will fail, though:
SomeObj &bar3 = foo();
}

and I will get two different objects back, both valid. (As commented,
the third will fail with some obscure warning about temporary objects -
but there's no copying taking place, so there's no temp object, AFAIK).
Hm.

So I checked something else:

void foobar()
{
SomeObj IamUseless;
}

That object is actually constructed, but destroyed after the function.
Now I am a bit unsure how the C++ compiler can determine EACH TIME
which object gets returned (maybe in the depth of an STL container of
whatever), and which one does NOT, so this has to be destroyed.

Usually I use new() for factories, but I wondered, and I checked. And I
don't get it :) . So maybe there's a guru in here who can help me out
with some background information? ;)


Cheers & thanks,
Axel.
 
I

Ian Collins

Axel said:
Hi all,

I am trying to get my head around what happens if I return a class
object from a function.
It seems C++ (MinGW) does not invoke the copy constructor if I do
something like this:

SomeObj foo()
{
SomeObj X;
// ....
return X;
}
The compiler is free to optimise away the redundant copy in this case.
BUT it does create a new object every time the function is invoked, and
it does seem like these objects are usable (I created a little test
program checking this :).
So basically I can go in main:

int main()
{
// ....
SomeObj bar = foo();
SomeObj bar2 = foo();
// for some strange reason the next thing will fail, though:
SomeObj &bar3 = foo();

You can't bind a temporary object (the function return) to a non-const
reference. Try

const SomeObj &bar3 = foo();
 
A

Axel Bock

Hi Ian,

you're right, this works. But why should that return value be a const?
I have declared const nowhere in the whole thing, and there's no
copying between the return value and the used object in main. So I
would say the function's return value is in fact not a temporary
object, and it being const is ... weird. I can modify bar and bar2 all
right ... :)

Basically I wonder about the object created in foo(), because should be
on the stack, in my opinion, and should be destroyed after the
function's scope ends.

But thanks, that answers one question, although replacing it with
another ;)

cheers,
Axel.
 
M

mwhooker

Alex,
I hope this answers your question. When an object goes out of scope, it
is destroyed. This doesn't necessarily mean that the memory is erased,
but if you decide to use the object, the behavior is undefined. So
while the object may still be on the stack, there are no guarantees
that it won't be overwriten.

-matt
 
K

klaus hoffmann

Axel said:
Hi Ian,


Basically I wonder about the object created in foo(), because should be
on the stack, in my opinion, and should be destroyed after the
function's scope ends.

But thanks, that answers one question, although replacing it with
another ;)

cheers,
Axel.
Look for return value optimization.

SomeObj foo()
{
SomeObj X;
return X;
}


int main()
{
// ....
SomeObj bar = foo();
SomeObj bar2 = foo();
// for some strange reason the next thing will fail, though:
SomeObj &bar3 = foo();
}

The memory for bar and bar2 is allocated in main, and foo executes the
constructor for this object.
hth
Klaus
 
M

Markus Schoder

klaus said:
Look for return value optimization.

SomeObj foo()
{
SomeObj X;
return X;
}

Is return value optimization allowed in this case? I thought it was
only possible for something like

SomeObj foo()
{
return SomeObj();
}

that is an unnamed object. Not sure though.
 
T

Tomás

Markus Schoder posted:
Is return value optimization allowed in this case? I thought it was
only possible for something like

SomeObj foo()
{
return SomeObj();
}

that is an unnamed object. Not sure though.


Yes, it's allowed in both cases above. If the expression after "return" can
used as an l-value, then you're likely to see optimization. For instance:

SomeClassType foo()
{
SomeObj x;

return ++x; //Likely to see optimization
}

SomeClassType foo()
{
SomeObj x;

return x++; //Less likely to see optimization
}


-Tomás
 
M

Markus Schoder

Tomás said:
Markus Schoder posted:


Yes, it's allowed in both cases above. If the expression after "return" can
used as an l-value, then you're likely to see optimization. For instance:

SomeClassType foo()
{
SomeObj x;

return ++x; //Likely to see optimization
}

SomeClassType foo()
{
SomeObj x;

return x++; //Less likely to see optimization
}

Checked this now in the Standard. According to 12.8 (15) your examples
both cannot be optimized but the original one (with just return x;) can
indeed. The expression in the return statement has to be the name of a
local object.
 
A

Axel Bock

Hi all,

thanks for all the answers.
What I would like to clarify now is the "style" of that code: Is that
good style, or not?

I would rather say it's not, cause it surely LOOKS like a local object,
so using that is confusing. Maybe even compiler-dependent, for it is an
"optimization"? Anyways I do appreciate the ease of the assignment: To
have a function in which some init / creation / factory stuff can be
done, and assigning the return value without using copy constructors or
pointers. That on the other hand is good. Hm.

:)

Anyways, cheers and thanks,
Axel.
 
P

peter koch

Axel said:
Hi all,

thanks for all the answers.
What I would like to clarify now is the "style" of that code: Is that
good style, or not?

It is good style.
I would rather say it's not, cause it surely LOOKS like a local object,
so using that is confusing.

But it IS a local object. I see nothing confusing about that. What
would be truly confusing would be if assignment created a different
object than copy construction.
Maybe even compiler-dependent, for it is an
"optimization"?

You might call it an optimization, but one employed by all compilers i
know. Still the optimization should not change anything for the reasons
stated above.
Anyways I do appreciate the ease of the assignment: To
have a function in which some init / creation / factory stuff can be
done, and assigning the return value without using copy constructors or
pointers. That on the other hand is good. Hm.

/Peter
 
A

Axel Bock

Hi Peter,
But it IS a local object. I see nothing confusing about that. What
would be truly confusing would be if assignment created a different
object than copy construction.

Sorry to disagree, but I don't think this is a local object. What I
understand from what was said in the thread this object is available
globally (read: in main) after the function exits, and it is perfectly
legal in being so. So it _looks_ like one, but it is not destroyed
after scope exit, which makes it somewhat not-local in my eyes :) .
(Mind: I am talking about the _object_, not the C++-variable holding it
in the function).
You might call it an optimization, but one employed by all compilers i
know. Still the optimization should not change anything for the reasons
stated above.

Ok, that's a point, so this can be considered safe to use. Thanks :)


Cheers,
Axel.
 
S

Squeamizh

Ian said:
You can't bind a temporary object (the function return) to a non-const
reference. Try

const SomeObj &bar3 = foo();

Wait, now I'm confused. Won't reading the value of bar3 yield
undefined behavior, since it references a temporary that went out of
scope?
 
T

Tomás

What I would like to clarify now is the "style" of that code: Is that
good style, or not?

I think it's bad style to do the following:

SomeClassType object = FuncReturnsByValue();

For two reasons:

1) You must rely on an optimization if you don't want any copies.
2) The copy constructor must be public.

There are two workarounds. Here's the less favourable method:

std::auto_ptr<SomeClassType> Func()
{
std::auto_ptr<SomeClassType> p = new SomeClassType;

//Play with the object

return p;
}

int main()
{
std::auto_ptr<SomeClassType> p = Func();
}


The other option, the option which I advocate, is to use a class.

class Manipulator
{
SomeObj object;

Manipulator() : object( whatever )
{
//Play with the object in here
}
}

int main()
{
Manipulator manipulated;
}


-Tomás
 
A

Axel Bock

Hi Tomas,

still, if you wanted a factory function, you should be able to write
one :) . Sometimes you want to create an object based on certain
external factors. If this action takes place in, say, 23 places in your
code, writing a function for that would be normal.

@Squeamizh: Confused is what I was. Still, as I understood, what we
face here is a so-called "return-value-optimization". In that case the
compiler recognizes that this object you return gets assigned to a ...
variable in the calling (!) code, so it will perform all the actions,
but the resulting object will be global. Or in other words: Doing a

SomeObj obj = foo();

translates to something like:

SomeObj &obj;
if (this_or_that) {
// .... yadda ....
obj = /* something smart */
}
else if (blah ...)
{
//.....
obj = /* whatever */
}
/* basically all your foo-code */

(very, very roughly) explained. And all this because the compiler
actually realizes that the _local_ object gets returned and is to be
used in a wider scope.
 
I

Ian Collins

Squeamizh said:
Wait, now I'm confused. Won't reading the value of bar3 yield
undefined behavior, since it references a temporary that went out of
scope?
Binding it to a const reference keeps it in scope.
 
O

Old Wolf

Axel said:
Hi Ian,

you're right, this works. But why should that return value be a const?

It isn't.
I have declared const nowhere in the whole thing, and there's no
copying between the return value and the used object in main.

There is copying. The compiler is allowed to perform an
optimization so it does not actually have to move bits around,
but the object management rules of C++ behave as if there is
a copy in all cases (it would be silly if legal code sometimes
worked and sometimes did not work based on whether a
compiler was optimizing or not).
So I would say the function's return value is in fact not a temporary
object,

The return value is a temporary object (aka. an rvalue). The fact that
the compiler may perform an optimization on it, does not change
anything.
and it being const is ... weird. I can modify bar and bar2 all
right ... :)

It isn't const. You can modify it. If you write:
foo().x = 3;
then there is no problem.

The original code was something like:
SomeObj const &bar3 = foo();

bar3 is a const reference. This means that you cannot modify
the referred-to object via this reference. The object itself is not
const.

Here's another example:

int i = 1;
int const &j = i;
j = 2; // illegal
i = 2; // legal

In this case 'i' is not const, even though there is a const
reference to it in existence.

Basically I wonder about the object created in foo(), because should
be on the stack, in my opinion, and should be destroyed after the
function's scope ends.

When you bind a temporary object to a reference, its lifetime
gets extended to match the scope of the reference (ie. the
object won't be destroyed until after the reference name goes
out of scope).

The rationale for not allowing binding non-const references to
temporary objects goes something like this:

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

long x = 1;
foo(x);
cout << "x is " << x << endl;

You might expect this to output 2 but in fact it outputs 1.
There is no function foo() that takes a long, so the compiler
creates a temporary int which is initialized from x, and binds
this temporary int to 'i'. Then the temporary int is incremented,
and destroyed when foo() returns. The value of x is unchanged
by this. By banning binding of temporary objects to non-const
references, this situation would generate a compiler error.
 
R

Roland Pibinger

The compiler is allowed to perform an
optimization so it does not actually have to move bits around,
but the object management rules of C++ behave as if there is
a copy in all cases (it would be silly if legal code sometimes
worked and sometimes did not work based on whether a
compiler was optimizing or not).

The compiler may elide copy constructor and destructor calls even if
they have side effects. In that case the C++ program behaves
differently dependent on the 'optimization', e.g.

class Test {
static int i;
public:
Test (const Test& t) {
++i;
}
// ...
};

The actual value of i is different when the the program is compiled
with RVO (and when RVO kicks in).
IMO, programs should return by value only 'leightweight' value objects
for which the 'savings' of RVO are irrelevant anyway.

Best wishes,
Roland Pibinger
 
G

Gavin Deane

Axel said:
Hi Peter,


Sorry to disagree, but I don't think this is a local object. What I
understand from what was said in the thread this object is available
globally (read: in main) after the function exits, and it is perfectly
legal in being so. So it _looks_ like one, but it is not destroyed
after scope exit, which makes it somewhat not-local in my eyes :) .

Here is a cut down version of your original code

SomeObj foo()
{
SomeObj X;
return X;
}

int main()
{
SomeObj bar = foo();
}

You asked whether this was good style. Compilers don't care about style
- they only care about correctness. A compiler will accept poor style
exactly as readily as good style as long as the code is correct. It is
programmers who care about style.

So what does a programmer see in the above code snippet? There are two
completely separate objects of type SomeObj in the code. One is called
X and is local to the function foo. The other is called bar and is
local to the function main. X is destroyed at the end of foo. bar is
destroyed at the end of main. The programmer can perform SomeObj
operations on X within foo and can perform SomeObj operations on bar
within main. But X can not be accessed within main because it does not
exist there.

So what can a programmer do differently if they know return value
optimisation will be performed? Nothing. There is no code you can write
that would only be correct if return value optimisation were performed.
There is nothing you can write in main to violate the scoping rules
that apply to X and bar if the optimisation is performed. So there is
no problem with this style.

Gavin Deane
 
R

Richard Herring

Axel said:
you're right, this works. But why should that return value be a const?

The return value isn't; the reference is. You've tripped over an
arbitrary rule that says "thou shall not bind a temporary to a non-const
reference". It's arbitrary in the sense that deleting it wouldn't cause
inconsistencies elsewhere, but it's often a sign of a misunderstanding
or a coding error. So imposing the rule probably saves programmers from
more problems than it causes.
I have declared const nowhere in the whole thing, and there's no
copying between the return value and the used object in main.

There is (in effect, though it may not cause the copy constructor to be
called) a copy from the return value to the temporary object that the
reference is bound to.
So I
would say the function's return value is in fact not a temporary
object,

What's its name? If it doesn't have a name, it's temporary.
and it being const is ... weird.

You can always convert a non-const reference to a const one.
I can modify bar and bar2 all
right ... :)

Sure. They are named objects. But bar3 is just a reference to an unnamed
temporary that's about to evaporate. What would be the point of
modifying it? OK, sometimes it would make sense, but that's the
reasoning behind the rule.
 
R

Roland Pibinger

So what can a programmer do differently if they know return value
optimisation will be performed? Nothing. There is no code you can write
that would only be correct if return value optimisation were performed.
There is nothing you can write in main to violate the scoping rules
that apply to X and bar if the optimisation is performed. So there is
no problem with this style.

The problem is that you ignore possible side effects in the copy
constructor and the destructor.

Regards,
Roland Pibinger
 

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,763
Messages
2,569,562
Members
45,039
Latest member
CasimiraVa

Latest Threads

Top