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::stringobject 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::strings, they understand, a std::stringconstructor 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::stringdestructor, too. Here's what happens at runtime in the call to push_back:
1. A temporary std::stringobject is created from the string literal "xyzzy". This object has no name; we'll call it temp . Construction of temp is the first std::stringconstruction. 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 xis 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 xinto the std::vectoris 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_backreturns, temp is destroyed, thus calling the std::stringdestructor.
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::stringobject 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_backis the wrong function. The function you want is emplace_back.
emplace_backdoes exactly what we desire: it uses whatever arguments are passed to it to construct a std::stringdirectly inside the std::vector. No temporaries are involved:
vs. emplace_back("xyzzy"); // construct std::string inside
// vs directly from "xyzzy"
emplace_backuses 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::stringin vsvia the std::stringconstructor 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_backis available for every standard container that supports push_back. Similarly, every standard container that supports push_frontsupports emplace_front. And every standard container that supports insert(which is all but std::forward_listand std::array) supports emplace. The associative containers offer emplace_hintto complement their insertfunctions that take a “hint” iterator, and std::forward_listhas emplace_afterto 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::stringwith 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::stringgoes into a location already occupied by an object, it's a different story. Consider:
Читать дальше