Is there a functional difference between:
void foo(const Bar& bar) {
Bar bar_copy(bar);
// Do stuff with bar_copy
}
and
void foo(Bar bar) {
// Do stuff with bar
}
Is there a functional difference between:
void foo(const Bar& bar) {
Bar bar_copy(bar);
// Do stuff with bar_copy
}
and
void foo(Bar bar) {
// Do stuff with bar
}
Yes, there is a valuable difference.
void foo(Bar bar)
can copy-construct or move-construct bar
, depending on the calling context.
And when a temporary is passed to foo(Bar bar)
, your compiler may be able to construct that temporary directly where bar
is expected to be. Hat tip to template boy.
Your function void foo(const Bar& bar)
always performs a copy.
Your function void foo(Bar bar)
may perform a copy or a move or possibly neither.
Yes, there are differences. While the most obvious one is that the type of the function changes (and thus the type of its function pointer), there are also some less obvious implications:
Bar
For example, assume the following call to foo
:
foo(Bar());
For the first version, this will be passed by a reference to const bar
and then copied using the copy constructor. For the second version, the compiler will attempt the move-constructor first.
This means, that the only the second version can be called by types that are move-constructible only, like std::unique_ptr
. In fact, the manually forced copy will not even allow compilation of the function.
Obviously, this can be mitigated by adding a slight complication:
void foo(Bar&& bar) {
// Do something with bar.
// As it is an rvalue-reference, you need not copy it.
}
void foo(Bar const& bar) {
Bar bar_copy(bar);
foo(std::move(bar_copy));
}
Interestingly, there is another difference: The context in which access permissions are checked.
Consider the following Bar
:
class Bar
{
Bar(Bar const&) = default;
Bar(Bar&&) = default;
public:
Bar() = default;
friend int main();
};
Now, the reference-and-copy version will error out, while the parameter-as-value version will not complain:
void fooA(const Bar& bar)
{
//Bar bar_copy(bar); // error: 'constexpr Bar::Bar(const Bar&)' is private
}
void fooB(Bar bar) { } // OK
Since we have declared main
to be a friend, the following call is allowed (note that the friend would not be necessary if, e.g. the actual call was made in a static
member function of Bar
):
int main()
{
fooB(Bar()); // OK: Main is friend
}
Bar
at the call siteAs has been observed in the comments, if you wish Bar
to be an incomplete type at the call site, it is possible to use the pass-by-reference version, as this does not require the call site to be able to allocate an object of type Bar
.
C++11 12.8/31:
When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the copy/move constructor and/or destructor for the object have side effects. In such cases, the implementation treats the source and target of the omitted copy/move operation as simply two different ways of referring to the same object [...]
- [...]
- when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same cv-unqualified type, the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move
- [...]
Obviously, only the call-by-value version meets this criterium - after passing by reference, the parameter is bound to a reference after all. Beyond an observable difference, this also means that an optimization oppurtunity is lost.
There are some differences.
void foo(const Bar& bar) {
Bar bar_copy(bar);
// Do stuff with bar_copy
}
doesn't allow to avoid the copy even if bar
was a temporary and avoid also the move of bar
.