C + + you should consider the place operation rather than the insert operation -- C++2.0 knowledge supplement

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);

Keywords: C++

Added by ATLien on Mon, 29 Nov 2021 16:00:46 +0200