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,
--
Niels Dekker
http://www.xs4all.nl/~nd/dekkerware
Scientific programmer at LKEB, Leiden University Medical Center
> 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. :)
It may or may not be more efficient to return const &. It definitely
affects the interface (you can't return something created on the fly in
the function). This can be particularly relevant when you're dealing
with virtual functions - you're imposing an implementation constraint on
derived classes if you choose const &. That's not to say that you
shouldn't return things by const & when it's appropriate, but it's not
just efficiency you need to consider.
Regards,
Stu
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() );
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
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
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. The
only difference that I can see between #1 and #2 is that the resulting
object has a name.
--
Pete
Roundhouse Consulting, Ltd. (www.versatilecoding.com) Author of
"The Standard C++ Library Extensions: a Tutorial and Reference"
(www.petebecker.com/tr1book)
>> class Foo { /* ... */ };
>> Foo GetFoo(void);
>>
>> const Foo& constReference = GetFoo(); // Choice #1
>> const Foo constValue = GetFoo(); // Choice #2
> 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.
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...
Oops, my bad, sorry - I read what I wanted to read for some reason
rather than what was there. Ignore what I said :)
>> 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).
Pete Becker wrote:
> 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
Okay, when you tell the compiler not to take advantage of legal
optimizations, it doesn't do them. <g> I'd rather spend my time writing
code than figuring out how to work around suboptimal compiler option
settings.
> 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
True, it's an ABI issue. Sun Studio 12, for example, uses a trick
similar to that of gcc.
--
Max
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.
* 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.
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
--
Due to hosting requirements I need visits to <url: http://alfps.izfree.com/>.
No ads, and there is some C++ stuff! :-) Just going there is good. Linking
to it is even better! Thanks in advance!
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.
Alf P. Steinbach wrote:
> 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
Also, a compiler might use a simple stack_pointer+offset to get a
pointer to a by-value copy, but have to waste a register to handle a
reference (or worse, a pointer on the stack, thus slowing every access).
The prime principle should be aimed at readability and safety, which a
by-value copy achieves much better. If a function for some reason
returns a reference as well, saving the result into a reference means
that the rest of the code in the function must be careful not to access
the referred-to object beyond its validity (either because its lifetime
ends, or its value changes). Making a copy eliminates all these concerns.
[...]
> > 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.
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.)
--
James Kanze (GABI Software) email:james...@gmail.com
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
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
James Kanze wrote:
> "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
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
> 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.)
> > Most of the time, objects which support inheritance will
> > have identity. (Do note the extensive qualifiers in the
> > above.
> 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.
> > There are certainly exceptions, and not a few.)
> 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.
James Kanze wrote:
> 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".
>>> There are certainly exceptions, and not a few.)
>
>> 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.
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
Oops, I mean:
http://home.roadrunner.com/~hinnant/issue_review/lwg-active.html#471
-- Niels
> 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.
> > (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".
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:-).
> >>> There are certainly exceptions, and not a few.)
> >> 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.
> 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.)