C++2.0 knowledge supplement
1 in some cases, placement rather than insertion is considered
1.1 container with placement operation
- 1,emplace_back can be used to support any push_ Standard container for back;
- 2,emplace_front can be used to support any push_ Standard container of front;
- 3. Any standard container that supports insert operations (except std::forward_list and std::array) supports insert operations;
- 4. The associated container provides empty_ Hint to supplement the insert function with hint iterator;
- 5,std::forward_list also has empty_ After to supplement insert_after
1.2 dominant conditions for implantation
The built-in function is likely to run faster when the following conditions are true:
- 1. The value to be added is added to the container by construction rather than assignment;
- Node based containers almost always use constructs to add new values, as well as non node based std::vector, std::deque and std::string (std::array is not node based, but it does not support insertion and placement).
- In a non node based container, replace_ Back uses the construction of non assignment to reduce the new value in place, and the empty of std::deque_ Front is also established.
- 2. The type of argument passed is different from that of the object held by the container;
- Only when elements of different types are inserted into the container can temporary objects be created and destructed, which can reflect the advantages of placement operation.
- 3. The container will not reject the value to be added because there are duplicate values.
- If the container does not allow duplicate values, the placement operation will create a node with the new value and compare it with the existing node of the container. If the value already exists, the placement operation will be aborted and the node will be destructed, which means that the cost of construction and destruct is wasted.
The example code of Clause 1 is as follows:
Validity difference (create temporary object):
std::vector<std::string> vs; //push_back version vs.push_back("xyz") //The version the compiler actually sees //Create a temporary object of type std::string and pass it to push_back vs.push_back(std::string("xyc")) //emplace_back version //Starting from xyz directly, construct std::string type objects in vs vs.emplace_back("xyz")
Examples with the same effect (no need to create temporary objects or fictional temporary objects):
std::vector<std::string> vs; std::string str("hello") vs.push_back(str) vs.emplace_back(str)
Add the container by assignment (the advantage disappears):
std::vector<std::string> vs; vs.emplace(vs.begin(), "xyz");
1.3 precautions
There are two questions worth considering whether to select the placement function:
- 1. Resource related problems (memory leakage may occur when operating the resource management object container [expressions such as new object name should not be passed to most functions such as empty_back and push_back]);
- 2. The insertion function may execute types that will be rejected in the insertion function (interaction with destructors decorated with explicit declarations) [ensure that correct parameters are passed].
Article 1 example code:
Call push_back: create std::shared_ptr < widget > temporary object, which is used to hold the raw pointer returned from the new Widget. Even if the memory is insufficient, the insertion fails, STD:: shared_ The destructor of PTR will also release temporary objects;
Call empty_ Back version: the raw pointer returned by the new Widget is perfectly forwarded, but if the memory is insufficient, the placement fails, the pointer to the Widget object is lost, and the Widget object on the heap will be leaked.
class Widget{}; void killWidget(Widget* pWidget){} int main(){ std::list<std::shared_ptr<Widget>> ptrs; //push_back version ptrs.push_back(std::shared_ptr<Widget>(new Widget, killWidget)); ptrs.push_back({new Widget, killWidget}); //emplace_back version ptrs.emplace_back(std::shared_ptr<Widget>(new Widget, killWidget)); ptrs.emplace_back(new Widget, killWidget); }
Solution to Clause 1 (but there is little difference between the versions of empty_back and push_back):
class Widget{}; void killWidget(Widget* pWidget){} int main(){ std::list<std::shared_ptr<Widget>> ptrs; std::shared_ptr<Widget> spw(new Widget, killWidget);//Construct Widget and manage with spw //push_back version //ptrs.push_back(std::move(spw)); //emplace_back version ptrs.emplace_back(std::move(spw)); }
Article 2 example code:
Call push_back: compilation failed because the std::regex constructor of const char * pointer type is declared with explicit, and the type conversion is blocked.
Call empty_ Back: the argument of a constructor is passed to the std::regex constructor (it is not regarded as implicit type conversion). The compiler seems to be equivalent to std::regex r(nullptr);, In this way, although it can be compiled, std::regex accepts a meaningless null pointer, and the problem will be hidden.
//The next line of code cannot be compiled because copy initialization prohibits the use of that constructor std::vector<std::regex> regexs; //regexs.push_back(nullptr);//error: no matching member function for call to 'push_back' //The following code is compiled normally //Direct initialization allows the use of std::regex constructors that accept pointers with explicit declarations regexs.emplace_back(nullptr);
Detailed explanation of compilation results: push_back and empty_ Back corresponds to copy initialization and direct initialization respectively, and copy initialization does not allow calling constructors decorated with explicit declarations.
//Replication initialization std::regex r1 = nullptr;//report errors //Direct initialization std::regex r2(nullptr);