compilers see a mismatch between the type of the argument ( const char[6]
) and the type of the parameter taken by push_back
(a reference to a std::string
). They address the mismatch by generating code to create a temporary std::string
object from the string literal, and they pass that temporary object to push_back
. In other words, they treat the call as if it had been written like this:
vs.push_back( std::string("xyzzy" )); // create temp, std::string
// and pass it to push_back
The code compiles and runs, and everybody goes home happy. Everybody except the performance freaks, that is, because the performance freaks recognize that this code isn't as efficient as it should be.
To create a new element in a container of std::string
s, they understand, a std::string
constructor is going to have to be called, but the code above doesn't make just one constructor call. It makes two. And it calls the std::string
destructor, too. Here's what happens at runtime in the call to push_bac
k:
1. A temporary std::string
object is created from the string literal "xyzzy"
. This object has no name; we'll call it temp
. Construction of temp
is the first std::string
construction. Because it's a temporary object, temp
is an rvalue.
2. temp
is passed to the rvalue overload for push_back
, where it's bound to the rvalue reference parameter x
. A copy of x
is then constructed in the memory for the std::vector
. This construction — the second one — is what actually creates a new object inside the std::vector
. (The constructor that's used to copy x
into the std::vector
is the move constructor, because x
, being an rvalue reference, gets cast to an rvalue before it's copied. For information about the casting of rvalue reference parameters to rvalues, see Item 25.)
3. Immediately after push_back
returns, temp is destroyed, thus calling the std::string
destructor.
The performance freaks can't help but notice that if there were a way to take the string literal and pass it directly to the code in step 2 that constructs the std::string
object inside the std::vector
, we could avoid constructing and destroying temp
. That would be maximally efficient, and even the performance freaks could contentedly decamp.
Because you're a C++ programmer, there's an above-average chance you're a performance freak. If you're not, you're still probably sympathetic to their point of view. (If you're not at all interested in performance, shouldn't you be in the Python room down the hall?) So I'm pleased to tell you that there is a way to do exactly what is needed for maximal efficiency in the call to push_back
. It's to not call push_back
. push_back
is the wrong function. The function you want is emplace_back
.
emplace_back
does exactly what we desire: it uses whatever arguments are passed to it to construct a std::string
directly inside the std::vector
. No temporaries are involved:
vs. emplace_back("xyzzy"); // construct std::string inside
// vs directly from "xyzzy"
emplace_back
uses perfect forwarding, so, as long as you don't bump into one of perfect forwarding's limitations (see Item 30), you can pass any number of arguments of any combination of types through emplace_back
. For example, if you'd like to create a std::string
in vs
via the std::string
constructor taking a character and a repeat count, this would do it:
vs.emplace_back(50, 'x'); // insert std::string consisting
// of 50 'x' characters
emplace_back
is available for every standard container that supports push_back
. Similarly, every standard container that supports push_front
supports emplace_front
. And every standard container that supports insert
(which is all but std::forward_list
and std::array
) supports emplace
. The associative containers offer emplace_hint
to complement their insert
functions that take a “hint” iterator, and std::forward_list
has emplace_after
to match its insert_after.
What makes it possible for emplacement functions to outperform insertion functions is their more flexible interface. Insertion functions take objects to be inserted , while emplacement functions take constructor arguments for objects to be inserted . This difference permits emplacement functions to avoid the creation and destruction of temporary objects that insertion functions can necessitate.
Because an argument of the type held by the container can be passed to an emplacement function (the argument thus causes the function to perform copy or move construction), emplacement can be used even when an insertion function would require no temporary. In that case, insertion and emplacement do essentially the same thing. For example, given
std::string queenOfDisco("Donna Summer");
both of the following calls are valid, and both have the same net effect on the container:
vs. push_back(queenOfDisco); // copy-construct queenOfDisco
// at end of vs
vs. emplace_back(queenOfDisco); // ditto
Emplacement functions can thus do everything insertion functions can. They sometimes do it more efficiently, and, at least in theory, they should never do it less efficiently. So why not use them all the time?
Because, as the saying goes, in theory, there's no difference between theory and practice, but in practice, there is. With current implementations of the Standard Library, there are situations where, as expected, emplacement outperforms insertion, but, sadly, there are also situations where the insertion functions run faster. Such situations are not easy to characterize, because they depend on the types of arguments being passed, the containers being used, the locations in the containers where insertion or emplacement is requested, the exception safety of the contained types' constructors, and, for containers where duplicate values are prohibited (i.e., std::set
, std::map
, std::unordered_set
, std::unordered_map
), whether the value to be added is already in the container. The usual performance-tuning advice thus applies: to determine whether emplacement or insertion runs faster, benchmark them both.
That's not very satisfying, of course, so you'll be pleased to learn that there's a heuristic that can help you identify situations where emplacement functions are most likely to be worthwhile. If all the following are true, emplacement will almost certainly outperform insertion:
• The value being added is constructed into the container, not assigned. The example that opened this Item (adding a std::string
with the value "xyzzy"
to a std::vector vs
) showed the value being added to the end of vs
— to a place where no object yet existed. The new value therefore had to be constructed into the std::vector
. If we revise the example such that the new std::string
goes into a location already occupied by an object, it's a different story. Consider:
Читать дальше