Sometimes we want to ‘sink’ a function argument when we call a function:
class Widget {
public:
// Both of these functions are going to make a copy of the in parameter
// This function makes a copy via the copy constructor
void add (const std::string& in) { strings.push_back (in); }
// This function makes a copy via the move constructor
void add (std::string&& in) { strings.push_back (std::move (in)); }
private:
std::vector<std::string> strings;
};
Writing two copies of a function which both do essentially the same thing is awkward to maintain and increases the size of the resulting binary.
We could write a single function taking a forwarding reference instead, but that has drawbacks like
std::strings
at all (but are
convertible)Another option is to write a single function which accepts its argument by value:
void add (std::string in) { strings.push_back (std::move (in)); }
This version allows us to hide the function implementation, and ensures we only have a single version of the function in the resulting binary. Also, lvalues are copied and rvalues are moved, which is the behaviour we want. However, this approach has some performance implications.
in
must be constructed and then moved, which will always
incur one more move than approaches which pass by (forwarding/lvalue/rvalue) reference.
If you pass an object by value several layers up the stack, that could be a lot of extra
moves.string
), calling a copy constructor might be able to re-use previously
allocated memory, so passing by ref and then doing a copy assign will be faster than passing
by value (i.e. constructing a whole new object, incurring allocation overhead) and then moving
that object.When is passing by value acceptable?
move
d objects).std::array
of move-only objects will still be
slow.Emplacement functions like emplace_back
allow us to avoid the creation and destruction of
temporaries when constructing objects into containers. They should always be at least as efficient
as their push
variants, and sometimes will be more efficient. Unfortunately, in reality this isn’t
always the case, so profiling will be required if performance is a concern. However, in the
following cases, emplacement is all but guaranteed to be faster:
emplace
ing at an existing position)Emplacement has a couple of gotchas:
vector<shared_ptr<T>>
by passing in a new T
might leak, because the container
might find out it’s unable to reserve space for the new element and throw, before the pointer
is captured in a shared_ptr
instance. If you’re using raw pointers, it’s important to
immediately capture them in resource-management classes. Threading raw pointers through
function calls is a bad idea, always pass resource-managing wrappers instead.emplace
functions (especially when refactoring existing code!).