The best way to retrieve a returned value... by const reference?

  • Thread starter Niels Dekker - no reply address
  • Start date
N

Niels Dekker - no reply address

Suppose you're calling a function that returns an object "by value".
When const access to the returned value is sufficient, you have the
choice between binding the returned object to a const-reference, and
copying the object to a local (const) variable, using
copy-initialization:

class Foo { /* ... */ };
Foo GetFoo(void);

const Foo& constReference = GetFoo(); // Choice #1
const Foo constValue = GetFoo(); // Choice #2

Personally, I have the habbit to bind such an object to a
const-reference (choice #1). Thereby I hope to avoid an expensive
copy-construction, which /might/ take place when you use
copy-initialization (choice #2). Is it a common practice to do so?

So far I haven't been able to prove that choice #1 is really superior to
choice #2, though. I tried both MSVC 2008 SP1 and GCC 4.3.2, and I
couldn't find a performance difference. It appears that both compilers
do copy elision, whenever using copy-initialization to retrieve the
returned value (choice #2). But I'd rather not depend on a compiler
version specific optimization. (GCC actually allows switching off copy
elision by "-fno-elide-constructors".) What do you think? Do you have
an example in which one choice really outperforms the other?

Note that Igor Tandetnik and Alex Blekhman also gave me some useful
feedback at microsoft.public.vc.language, subject "Does binding to
const-reference outperform copy-initialization from returned value?", at
http://groups.google.com/group/microsoft.public.vc.language/browse_thread/thread/c009118b7057e547


Kind regards,
 
B

Balog Pal

Niels Dekker - no reply address said:
Suppose you're calling a function that returns an object "by value". When
const access to the returned value is sufficient, you have the choice
between binding the returned object to a const-reference, and copying the
object to a local (const) variable, using copy-initialization:

class Foo { /* ... */ };
Foo GetFoo(void);

const Foo& constReference = GetFoo(); // Choice #1
const Foo constValue = GetFoo(); // Choice #2

Personally, I have the habbit to bind such an object to a const-reference
(choice #1).

I too, often use that. Looking around the web it seems not a well-known
feature of C++ (that the life of bound object is extended to life of ref...)
but there are no other problems I'm aware of.
Thereby I hope to avoid an expensive copy-construction, which /might/ take
place when you use copy-initialization (choice #2). Is it a common
practice to do so?

So far I haven't been able to prove that choice #1 is really superior to
choice #2, though.

Note that the preferred form for that is not copy-init, but direct-init!

const Foo constValue(GetFoo());

Many optimizers can create identical code for all the three forms --
completely removing copies.
I tried both MSVC 2008 SP1 and GCC 4.3.2, and I couldn't find a
performance difference. It appears that both compilers do copy elision,
whenever using copy-initialization to retrieve the returned value (choice
#2).
Yeah.

But I'd rather not depend on a compiler version specific optimization.
(GCC actually allows switching off copy elision by
"-fno-elide-constructors".) What do you think? Do you have an example in
which one choice really outperforms the other?

Performance is not thing you speculate but one you measure with profiler.
Common observation is that bottlenecks are not at places programmers would
think...

Also they can move around depending on processor, cache, memory, etc.

IMO don't sweat it, inless you see some real point against using the ref
form, use that, it won't let you down. :)
 
S

SG

Suppose you're calling a function that returns an object "by value".
When const access to the returned value is sufficient, you have the
choice between binding the returned object to a const-reference, and
copying the object to a local (const) variable, using
copy-initialization:

  class Foo { /* ... */ };
  Foo GetFoo(void);

  const Foo& constReference = GetFoo(); // Choice #1
  const Foo constValue = GetFoo();  // Choice #2

Personally, I have the habbit to bind such an object to a
const-reference (choice #1). Thereby I hope to avoid an expensive
copy-construction, which /might/ take place when you use
copy-initialization (choice #2). Is it a common practice to do so?

Common practice? I don't think so. In practise, there's only one
advantage of #1 over #2: The function can return some derived type
that you don't like to spell out which would be subject to slicing in
#2.

I can't speak for Microsoft's compiler but GCC elides the copy in #2
regardless of the optimization settings. It's a matter of the ABI and
how return-by-value is implemented. (GCC passes the address of the
future "constValue" to GetFoo and inside GetFoo the object is directly
constructed at the given address -- assuming (N)RVO is applicable).

Though, I still hesitate to use return by value for objects with a
potentially expensive copy operation. But that's going to change with C
++0x. :)

Cheers!
SG
 
S

SG

C++03 section 8.5 paragraph 14 seems to state that in this case, direct
initialization MUST be used, rather than it being an optional
optimization. That is,

    Foo foo = GetFoo();

should be treated exactly the same as

    Foo foo( GetFoo() );

But GetFoo returns an rvalue which is then use as source for copying.
This is a case where the C++ standard allows a copy elision.

Cheers!
SG
 
B

Balog Pal

Stuart Golodetz said:
It may or may not be more efficient to return const &.

You didn't pay attention. The function returns Foo, not Foo& in any case,
the difference is only how the returned object is handled.
 
B

Balog Pal

blargg said:
C++03 section 8.5 paragraph 14 seems to state that in this case, direct
initialization MUST be used, rather than it being an optional
optimization. That is,

Foo foo = GetFoo();

should be treated exactly the same as

Foo foo( GetFoo() );

Rrright, this is a special case when the source type is similar to the
destination type -- the preference to use direct-init in general is for
uniformity, there is no need to make the separation, let alone make code
dependant on that...
 
N

Niels Dekker - no return address

Thanks to all of you for your replies so far!


Pete said:
I don't think that this avoids the copy. The returned value has to
live somewhere in the current stack frame, so it has to be copied
into a temporary object, where "copied" means the same things as in
#2.

When I use the option "-fno-elide-constructors" on GCC 4.3.2, choice #1
/does/ avoid a copy. The following example gets me two copy-constructor
calls for the initialization of constValue, and just one for constReference:

//////////////////////////////////////////////////
class Foo
{
public:
unsigned copyCount;

Foo(void)
:
copyCount(0)
{
}

Foo(const Foo& arg)
:
copyCount(arg.copyCount + 1)
{
}

Foo& operator=(const Foo& arg)
{
copyCount = arg.copyCount + 1;
return *this;
}
};

Foo GetFoo(void)
{
return Foo();
}

int main(void)
{
const Foo& constReference = GetFoo();
const Foo constValue = GetFoo();

// Returns 9 when doing "gcc-4 -fno-elide-constructors"
return constReference.copyCount +
(constValue.copyCount << 2);
}
/////////////////////////////////////////////////

So when using "-fno-elide-constructors", binding to const-reference appears
superior. But honestly, I don't think I would ever switch on this option
for production code... So I'm still hoping to find a more realistic
scenario in which binding to const-reference would outperform
copy-initialization. Otherwise maybe I should change my habit!

Kind regards, Niels
 
N

Niels Dekker - no reply address

Balog said:
"Niels Dekker - no reply address"
I too, often use that. Looking around the web it seems not a
well-known feature of C++ (that the life of bound object is extended
to life of ref...) but there are no other problems I'm aware of.
IMO don't sweat it, inless you see some real point against using the
ref form, use that, it won't let you down. :)

Thanks, Balog. Unfortunately there is a real point against this form:
if you're calling a virtual function of Foo through a reference to a
temporary Foo object, it won't be inlined by the compiler. At least, not
by MSVC 2008 SP1, as I concluded from a little test of mine. See also
http://groups.google.com/group/microsoft.public.vc.language/msg/e28eb112ae9dc08c


Kind regards, Niels
 
M

Maxim Yegorushkin

Common practice? I don't think so. In practise, there's only one
advantage of #1 over #2: The function can return some derived type
that you don't like to spell out which would be subject to slicing in
#2.

I can't speak for Microsoft's compiler but GCC elides the copy in #2
regardless of the optimization settings. It's a matter of the ABI and
how return-by-value is implemented. (GCC passes the address of the
future "constValue" to GetFoo and inside GetFoo the object is directly
constructed at the given address -- assuming (N)RVO is applicable).

True, it's an ABI issue. Sun Studio 12, for example, uses a trick
similar to that of gcc.
 
B

Balog Pal

"Niels Dekker
Thanks, Balog. Unfortunately there is a real point against this form: if
you're calling a virtual function of Foo through a reference to a
temporary Foo object, it won't be inlined by the compiler.

This leads to different fields. If you deal with hierarchy objects, and
copy them, ther is a danger of slicing. The reference form is safe wrt.
this, it preserves the dynamic type of the returned object, while the other
form forces it to the marked type.

The different code for calls comes from this same thing: if you have a
concrete object, function calls are nonvirtual (even for virtual function).
Therefore they can be inlined.

If there is a reference, the calls are virtual through the VMT. (Okay, the
compiler could follow how how the ref got initialized, but it's well beyond
trivial.)

My experience shows that object hierarchies are not so frequently used these
days in C++ (I mean correctly ;-) and where they are used, having the
virtual calls is hardly an issue to worry about.
 
A

Alf P. Steinbach

I decided to respond to the first post in cl++, and this was it.

* Balog Pal:
"Niels Dekker

This leads to different fields. If you deal with hierarchy objects, and
copy them, ther is a danger of slicing.

I think you mean a hierarchy of classes. :)

Anyways, you're right that many C++ class hierarchies, perhaps the great
majority, are evidently designed by folks who haven't understood that a
restriction to correct usage needs to be designed in.

In short, if slicing is a problem, then slicing shouldn't be allowed, by design.

The reference form is safe wrt.
this, it preserves the dynamic type of the returned object, while the other
form forces it to the marked type.

The problem with reference as a routine result is more the lifetime isssue and
the design issue of allowing the caller to use non-const methods.

How long is that reference valid?

Can the caller modify the object without breaking assumptions in the class of
the routine that provided the reference?

Will the design be practical with a restriction to const result?

Correctness and usability are far more important than micro-efficiency.

The different code for calls comes from this same thing: if you have a
concrete object, function calls are nonvirtual (even for virtual function).
Therefore they can be inlined.

If there is a reference, the calls are virtual through the VMT. (Okay, the
compiler could follow how how the ref got initialized, but it's well beyond
trivial.)

This focus on micro-efficiency is (in general) just evil premature optimization.

In general call efficiency should not be a concern.

It prevents you from focusing on issues that do matter (see above).

My experience shows that object hierarchies are not so frequently used these
days in C++ (I mean correctly ;-) and where they are used, having the
virtual calls is hardly an issue to worry about.

I think you mean a hierarchy of classes. :)


Cheers & hth.,

- Alf
 
N

Niels Dekker - no reply address

Balog said:
Niels Dekker [wrote]
Thanks, Balog. Unfortunately there is a real point against this
form: if you're calling a virtual function of Foo through a
reference to a temporary Foo object, it won't be inlined by the
compiler.

This leads to different fields. If you deal with hierarchy objects,
and copy them, ther is a danger of slicing. The reference form is
safe wrt. this, it preserves the dynamic type of the returned object,
while the other form forces it to the marked type.

The different code for calls comes from this same thing: if you have a
concrete object, function calls are nonvirtual (even for virtual
function). Therefore they can be inlined.

If there is a reference, the calls are virtual through the VMT. (Okay,
the compiler could follow how how the ref got initialized, but
it's well beyond trivial.)

I was actually considering to submit a ticket, to request such an
optimization for MSVC... but maybe I should just change my habit to
bind a local const-reference to an object returned by-value!
My experience shows that object hierarchies are not so frequently
used these days in C++ (I mean correctly ;-) and where they are
used, having the virtual calls is hardly an issue to worry about.

Virtual functions are here to stay, and I don't think it's "in general"
bad practice to return an object "by-value" whose type has virtual
functions.
Correctness and usability are far more important than
micro-efficiency. [...]
This focus on micro-efficiency is (in general) just evil premature
optimization.
In general call efficiency should not be a concern.
It prevents you from focusing on issues that do matter (see above).

While programming, I have some "habits", like doing ++i instead of i++,
and binding a const-reference to an object returned by-value, instead of
copy-initialization. Those habits allow me to focus on other issues that
matter. But now and then, I have to check whether those habits still
make sense, because of new insights, improved compiler optimizations,
new language features, etc.

Programming in a way that avoids unnecessary copying "by default" does
matter to me. But I'm just starting to realize that binding a local
const-reference to an object that is returned by-value, instead of
copy-initialization, doesn't get me any performance gain. Instead, it
might get me a small performance /penalty/, because of the extra virtual
function calls.

Anyway, thanks for your feedback,

Niels
 
J

James Kanze

Balog said:
Niels Dekker [wrote]

[...]
This is, of course, completely false.
Virtual functions are here to stay, and I don't think it's "in
general" bad practice to return an object "by-value" whose
type has virtual functions.

"In general", a type which is part of an inheritance hierarchy
shouldn't support copy and assignment. Most of the time,
objects which support inheritance will have identity. (Do not
the extensive qualifiers in the above. There are certainly
exceptions, and not a few.)
 
H

Howard Hinnant

Suppose you're calling a function that returns an object "by value".
When const access to the returned value is sufficient, you have the
choice between binding the returned object to a const-reference, and
copying the object to a local (const) variable, using
copy-initialization:

  class Foo { /* ... */ };
  Foo GetFoo(void);

  const Foo& constReference = GetFoo(); // Choice #1
  const Foo constValue = GetFoo();  // Choice #2

Personally, I have the habbit to bind such an object to a
const-reference (choice #1). Thereby I hope to avoid an expensive
copy-construction, which /might/ take place when you use
copy-initialization (choice #2). Is it a common practice to do so?

A minor addition to the rest of this thread:

One danger of this habit is accidently not realizing that GetFoo()
returns a const Foo&. For example it can be a bad idea to use this
style with std::max which returns a const&.

const Foo& cr = std::max(GetFoo(x), GetFoo(y));

Personally I consider this a design defect of std::max, and not a
reason to not use your style. I mention it only because there is
defectively designed code out there (like std::max) that you need to
watch out for.

-Howard
 
N

Niels Dekker - no return address

Virtual functions are here to stay, and I don't think it's "in
James said:
"In general", a type which is part of an inheritance hierarchy
shouldn't support copy and assignment.

Thanks, James. But personally I think it's okay for a "leaf" of an
inheritance hierarchy (a type that should be considered "sealed") to be
CopyConstructible.
Most of the time,
objects which support inheritance will have identity.
(Do not the extensive qualifiers in the above.

I'm sorry I don't really understand the above. Please explain!
There are certainly exceptions, and not a few.)

Like std::exception? ;-)


Kind regards, Niels
 
N

Niels Dekker - no reply address

Howard said:
One danger of this habit is accidently not realizing that GetFoo()
returns a const Foo&.

The GetFoo() function I'm calling returns "by value", but your comment still
appears applicable. :)
For example it can be a bad idea to use this
style with std::max which returns a const&.

const Foo& cr = std::max(GetFoo(x), GetFoo(y));

Personally I consider this a design defect of std::max, and not a
reason to not use your style. I mention it only because there is
defectively designed code out there (like std::max) that you need to
watch out for.

Just looking at an example from your paper, "Improved min/max",
www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2199.html

A a2(2);
const A& ar = std::min(A(1), a2);

I guess that's the very same problem you're talking about, getting a
reference to a destructed object. Thanks, Howard. I'll be extra cautious,
when binding returned values to references!


Kind regards, Niels
 
J

James Kanze

Thanks, James. But personally I think it's okay for a "leaf"
of an inheritance hierarchy (a type that should be considered
"sealed") to be CopyConstructible.

Most of the time, it probably doesn't hurt, if the type doesn't
have identity. But usually, there's no point in it, since
client code doesn't normally use the leaf classes anyway. (And
since copy was blocked in the base class, it would require extra
effort to unblock it.)
I'm sorry I don't really understand the above. Please explain!

In theory, polymorphism and identity are two separate,
orthogonal concepts, but in practice, polymorphism tends to be
used principally on classes with identity. At least in my
experience---it's certainly not a rigid rule.
Like std::exception? ;-)

Yes, and you can see the problems that can sometimes cause. You
can't pass an exception into a function for that function to
throw, because the thrown type is the static type. The reason,
of course, why the static type is thrown, rather than the
dynamic type, is that the compiler must allocate memory and
generate a call to the copy constructor; if exceptions weren't
to be copied, then throw could easily use the dynamic type.
 
N

Niels Dekker - no reply address

Thanks, James. But personally I think it's okay for a "leaf"
James said:
Most of the time, it probably doesn't hurt, if the type doesn't
have identity. But usually, there's no point in it, since
client code doesn't normally use the leaf classes anyway.

As a client, I quite often use leaf classes from other libraries (notably
Qt). I'm surprised that doing so might not be normal.

(And since copy was blocked in the base class, it would
require extra effort to unblock it.)

Assuming that you put extra effort to block copying in the base class, of
course... An abstract base class isn't CopyConstructible, but a derived
class may still get a compiler-generated copy-constructor "for free".

Yes, and you can see the problems that can sometimes cause. You
can't pass an exception into a function for that function to
throw, because the thrown type is the static type. The reason,
of course, why the static type is thrown, rather than the
dynamic type, is that the compiler must allocate memory and
generate a call to the copy constructor; if exceptions weren't
to be copied, then throw could easily use the dynamic type.

Okay. But still I'm not particularly happy about std::exception being
CopyConstructible. Because it's mostly used as a base class anyway. (I would
have liked it to be an abstract base class.) And because the effect of
copying an std::exception is rather unclear. Fortunately it looks like this
effect will be clarified, by the resolution of an LWG issue, submitted by
Martin Sebor: "result of what() implementation-defined"
http://home.roadrunner.com/~hinnant/issue_review/lwg-active.html#973


Kind regards, Niels
 
J

James Kanze

As a client, I quite often use leaf classes from other
libraries (notably Qt). I'm surprised that doing so might not
be normal.

I'm not familiar with Qt, but in the GUI work I've done (Java
Swing), I generally derived from the library classes, and passed
instances of derived classes back to the library. It was the
library which used the objects, so the shoe was on the other
foot. But you're right that in this case, your code is often
aware of the actual leaf class (and in a number of cases, the
library class can be used directly as a leaf---but the library
itself doesn't know that the type it's dealing with is actually
the final type).

On the other hand, of course, GUI classes almost always have
identity, and aren't copiable, so the question doesn't arise.
Assuming that you put extra effort to block copying in the
base class, of course... An abstract base class isn't
CopyConstructible, but a derived class may still get a
compiler-generated copy-constructor "for free".

Most of the time, my abstract base class will simply derive from
Gabi::Interface, which takes care of some of the more mundane
details, like ensuring that the destructor is virtual. And
making the object uncopiable:).
Okay. But still I'm not particularly happy about
std::exception being CopyConstructible. Because it's mostly
used as a base class anyway. (I would have liked it to be an
abstract base class.) And because the effect of copying an
std::exception is rather unclear. Fortunately it looks like
this effect will be clarified, by the resolution of an LWG
issue, submitted by Martin Sebor: "result of what()
implementation-defined"http://home.roadrunner.com/~hinnant/issue_review/lwg-active.html#973

The problem of copying is a bit awkward in the case of
exceptions. But copy you must, given the way the C++ object
model works. (On the other hand, I don't see any value in
supporting assignment.)
 

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,767
Messages
2,569,572
Members
45,046
Latest member
Gavizuho

Latest Threads

Top