In this talk by Sutter at 1:15:26 it was presented a code like below,
class employee{
std::string name_;
public:
template<class String, class=
std::enable_if_t< !std::is_same<std::decay_t<String>, std::string>::value > >
void set_name(String && name)
noexcept(std::isnothrow_assignable<std::string &, String>::value)
{
name_ = std::forward<String>(name);
}
}
I know how std::forward
works, if name
is a lvalue, name_
will get copy constructed; and if name
is a rvalue, name_
will get move constructed. But in the slide it also says that Optimized to steal from rvalues (and more)
, what is more?
And later it shows that this code seems the fastest among all the four implementations, especially for char *
, anybody has the patience to walk through this code and explain what more is being optimized and why it is the fastest, particularly in the case of char *
?
So the character array approach remains significantly faster although less so.
char is a primitive data type while String is a class. If you need to store just 1 character, using a char is always better as it would consume lesser memory also using a String to store 1 character is just unrequired as every String also has a lot of methods that are kind of irrelevant for a single character.
First of all, note that the code contains a typo and that the enable_if
constraint does not do what's discussed even with the typo removed; In particular the function just won't work with char*
, so obviously it's not the fastest with char*
. You can see a question I asked about this here along with a 'corrected' perfect forwarding setter (along with the endorsement of this version from Howard Hinnant).
template <class String>
auto set_name(String&& name)
-> decltype(name_ = std::forward<String>(name), void()) {
name_ = std::forward<String>(name);
}
The benchmark that's used for the presentation is calling set_name
repeatedly on an employee
in order to show the case where the member string
gets to reuse its capacity instead of there being a memory allocation every iteration. The difference between the tall bars and the short bars shown in the benchmarks is the difference between reusing capacity vs. doing an allocation every iteration.
The reason the corrected perfect forwarder is fast with char*
is because the instantiation of the template for char*
just forwards a char*
instead of needing to construct a string
param in order to call set_name
with an actual string
object parameter. The assignment inside the setter is fast because string
implements operator=(char*)
that does the efficient memcpy into its existing storage and extra allocations are avoided.
So the claim Optimized to steal from rvalues (and more)
is because perfect forwarding does nothing but forwarding. Not only does it pass through an rvalue if it gets an rvalue, but it also doesn't convert types, meaning that optimizations the "forwardee" implements such as string
's operator=(char*)
get exposed as well.
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