I want to develop a small polymorphic class with type erasure and I wonder which version of the templatized constructor is better and should be used.
We can pass by value:
class A
{
...
template< typename T >
A( T t ) { /* create the underlying model via std::move */ }
...
};
or we can use a universal reference:
class A
{
...
template< typename T >
A( T &&t ) { /* create the underlying model via std::forward */ }
...
};
(The universal reference has to be enabled if for the case that T
is not the class itself and the class is not copied). Any ideas? Both version look equal to me.
Use pass-by-reference if you want to modify the argument value in the calling function. Otherwise, use pass-by-value to pass arguments. The difference between pass-by-reference and pass-by-pointer is that pointers can be NULL or reassigned whereas references cannot.
2) For passing large sized arguments: If an argument is large, passing by reference (or pointer) is more efficient because only an address is really passed, not the entire object.
Universal reference was a term Scott Meyers coined to describe the concept of taking an rvalue reference to a cv-unqualified template parameter, which can then be deduced as either a value or an lvalue reference.
As a rule of thumb, passing by reference or pointer is typically faster than passing by value, if the amount of data passed by value is larger than the size of a pointer.
These are not equivalent and sometimes one is desired over the the other one. A stellar talk by Nicolai Josuttis is an hour worth of talking just about this. I highly recommend watching it at least once.
Personally, unless you encounter a special case, where conversions are expensive and you want to avoid temporaries as much as possible, I would suggest just passing by value and std::move
ing the argument.
Case where T&&
is more efficient:
struct foo {
std::string bar;
template <typename T>
foo(T&& t)
:bar(std::forward<T>(t)){}
};
versus:
struct foo {
std::string bar;
foo(std::string t)
:bar(std::move(t)){}
};
when you do:
int main() {
foo f("some char*");
}
In the first case (perfect forwarding) you simply construct a std::string
with const char*
argument. In the second case, you construct one temporary (t
from "some char*"
) and one empty std::string
object, then you apply one move operation. It's not the end of the world, but the first version is simply more efficient.
To be absolutely clear about performance:
The first version uses 1 allocation
the second version uses 1 allocation and 1 move
and by move I don't mean std::move
, since it generates no code (it's just a cast). By move I mean the code that needs to be execuded that actually moves from the string, which is a part of std::string(std::string&&)
.
Once again - the above example was based on the talk I linked at the beginning of the answer. It's really worth watching.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With