Frankly, you shouldn't be passing expressions like “ new Widget
” to emplace_back
or push_back
or most any other function, anyway, because, as Item 21explains, this leads to the possibility of exception safety problems of the kind we just examined. Closing the door requires taking the pointer from “ new Widget
” and turning it over to a resource-managing object in a standalone statement, then passing that object as an rvalue to the function you originally wanted to pass “ new Widget
” to. ( Item 21covers this technique in more detail.) The code using push_ back
should therefore be written more like this:
std::shared_ptr spw(new Widget, // create Widget and
killWidget);// have spw manage it
ptrs.push_back( std::move(spw)); // add spw as rvalue
The emplace_back
version is similar:
std::shared_ptr spw(new Widget, killWidget);
ptrs.emplace_back( std::move(spw));
Either way, the approach incurs the cost of creating and destroying spw
. Given that the motivation for choosing emplacement over insertion is to avoid the cost of a temporary object of the type held by the container, yet that's conceptually what spw
is, emplacement functions are unlikely to outperform insertion functions when you're adding resource-managing objects to a container and you follow the proper practice of ensuring that nothing can intervene between acquiring a resource and turning it over to a resource-managing object.
A second noteworthy aspect of emplacement functions is their interaction with explicit
constructors. In honor of C++11's support for regular expressions, suppose you create a container of regular expression objects:
std::vector regexes;
Distracted by your colleagues' quarreling over the ideal number of times per day to check one's Facebook account, you accidentally write the following seemingly meaningless code:
regexes. emplace_back(nullptr); // add nullptr to container
// of regexes?
You don't notice the error as you type it, and your compilers accept the code without complaint, so you end up wasting a bunch of time debugging. At some point, you discover that you have inserted a null pointer into your container of regular expressions. But how is that possible? Pointers aren't regular expressions, and if you tried to do something like this,
std::regex r = nullptr; // error! won't compile
compilers would reject your code. Interestingly, they would also reject it if you called push_back
instead of emplace_back
:
regexes. push_back(nullptr); // error! won't compile
The curious behavior you're experiencing stems from the fact that std::regex
objects can be constructed from character strings. That's what makes useful code like this legal:
std::regexupperCaseWord( "[A-Z]+");
Creation of a std::regex
from a character string can exact a comparatively large runtime cost, so, to minimize the likelihood that such an expense will be incurred unintentionally, the std::regex
constructor taking a const char*
pointer is explicit
. That's why these lines don't compile:
std::regex r = nullptr; // error! won't compile
regexes.push_back(nullptr); // error! won't compile
In both cases, we're requesting an implicit conversion from a pointer to a std::regex
, and the explicit
ness of that constructor prevents such conversions.
In the call to emplace_back
, however, we're not claiming to pass a std::regex
object. Instead, we're passing a constructor argument for a std::regex
object. That's not considered an implicit conversion request. Rather, it's viewed as if you'd written this code:
std::regex r(nullptr); // compiles
If the laconic comment “compiles” suggests a lack of enthusiasm, that's good, because this code, though it will compile, has undefined behavior. The std::regex
constructor taking a const char*
pointer requires that the pointed-to string comprise a valid regular expression, and the null pointer fails that requirement. If you write and compile such code, the best you can hope for is that it crashes at runtime. If you're not so lucky, you and your debugger could be in for a special bonding experience.
Setting aside push_back
, emplace_back
, and bonding for a moment, notice how these very similar initialization syntaxes yield different results:
std::regex r1 =nullptr; // error! won't compile
std::regex r2 (nullptr ); // compiles
In the official terminology of the Standard, the syntax used to initialize r1
(employing the equals sign) corresponds to what is known as copy initialization . In contrast, the syntax used to initialize r2
(with the parentheses, although braces may be used instead) yields what is called direct initialization . Copy initialization is not permitted to use explicit
constructors. Direct initialization is. That's why the line initializing r1
doesn't compile, but the line initializing r2
does.
But back to push_back
and emplace_back
and, more generally, the insertion functions versus the emplacement functions. Emplacement functions use direct initialization, which means they may use explicit
constructors. Insertion functions employ copy initialization, so they can't. Hence:
regexes. emplace_back(nullptr); // compiles. Direct init permits
// use of explicit std::regex
// ctor taking a pointer
regexes. push_back(nullptr); // error! copy init forbids
// use of that ctor
The lesson to take away is that when you use an emplacement function, be especially careful to make sure you're passing the correct arguments, because even explicit
constructors will be considered by compilers as they try to find a way to interpret your code as valid.
Things to Remember
• In principle, emplacement functions should sometimes be more efficient than their insertion counterparts, and they should never be less efficient.
• In practice, they're most likely to be faster when (1) the value being added is constructed into the container, not assigned; (2) the argument type(s) passed differ from the type held by the container; and (3) the container won't reject the value being added due to it being a duplicate.
• Emplacement functions may perform type conversions that would be rejected by insertion functions.
Scott Meyers is one of the world's foremost experts on C++. A sought-after trainer, consultant, and conference presenter, his Effective C++ books ( Effective C++ , More Effective C++ , and Effective STL ) have set the bar for C++ programming guidance for more than 20 years. He has a Ph.D. in computer science from Brown University. His website is aristeia.com .
The animal on the cover of Effective Modern C++ is a Rose-crowned fruit dove ( Ptilinopus regina ). This species of dove also goes by the names pink-capped fruit dove or Swainson's fruit dove. It is distinguished by its striking plumage: grey head and breast, orange belly, whitish throat, yellow-orange iris, and grey green bill and feet.
Читать дальше