简体   繁体   English

为什么emplace_back很重要?

[英]Why emplace_back does matter?

#include <iostream>
#include <vector>

struct T{
    T(){
        std::cout << "Constructor\n";
    }
    ~T(){
        std::cout << "Destructor\n";
    }   
};

int main() {
    std::vector<T> vec;
    vec.push_back(T());
    vec.push_back(T());

    return 0;
}

The output is: 输出为:

(1)Constructor
(2)Destructor
(3)Constructor
(4)Destructor
(5)Destructor
(6)Destructor
(7)Destructor

Why there is so much desructors calls? 为什么有如此多的节制调用? I see that: 我看到:

(1) consruct temporary object temp1 (1)构建临时对象temp1

(2) destruct temp1 (2)破坏temp1

(3) consruct temporary object temp2 (3)构造临时对象temp2

(4) destruct temp2 (4)破坏temp2

Then it was called copy constructor or move constructor for temp1 and temp 2. So, (5) and (6) are clear. 然后,它被称为temp1和temp 2的复制构造函数或move构造函数。因此,(5)和(6)很清楚。 But what about (7)? 但是(7)呢?

Let's expand your structure a bit: 让我们扩展一下结构:

struct T {
    T() {
        std::cout << "Constructor\n";
    }

    T(const T&) {
        std::cout << "Copy Constructor\n";
    }

    T(T&&) {
        std::cout << "Move Constructor\n";
    }

    ~T() {
        std::cout << "Destructor\n";
    }
};

And separate calls to push_back method: 并分别调用push_back方法:

vec.push_back(T()); // 1
std::cout << "--- --- ---\n";

vec.push_back(T()); // 2
std::cout << "--- --- ---\n";

Now the output looks more complete: 现在输出看起来更加完整:

Constructor
Move Constructor
Destructor
--- --- ---
Constructor
Move Constructor
Copy Constructor
Destructor
Destructor
--- --- ---
Destructor
Destructor

The first group: 第一组:

Constructor
Move Constructor
Destructor

corresponds to the first push_back call: 对应于第一个push_back调用:

vec.push_back(T()); // 1

The output might be decrypted easily: 输出可能很容易解密:

Constructor // Create a temporary
Move Constructor // Move a temporary into the internal vector storage
Destructor // Destroy a temporary

The second group: 第二组:

Constructor
Move Constructor
Copy Constructor
Destructor
Destructor

corresponds to the second push_back call: 对应于第二个push_back调用:

vec.push_back(T()); // 2

and a little bit more complicated: 稍微复杂一点:

Constructor // create a temporary
Move Constructor // move it into the newly allocated vector storage
Copy Constructor // copy previously created element into the new storage
Destructor // destroy old storage
Destructor // destroy temporary

Here you should remember that vector class allocates its memory internally and then manages it to provide enogh space for all elements. 在这里您应该记住, 向量类在内部分配其内存,然后对其进行管理以为所有元素提供足够的空间。 So, if you add more elements, new allocations happen and old elements are copied or moved into the new storage. 因此,如果添加更多元素,则会发生新分配,并且会将旧元素复制或移动到新存储中。

In case of known size you might use reserve method, which simply reserves enough memory for a particular number of elements. 在已知大小的情况下,您可以使用reserve方法,该方法只是为特定数量的元素保留足够的内存。 It allows to avoid unnecessary memory reallocations and copying or moving elements during these reallocations on adding new elements into the vector (at least until you don't exceed the reserved size). 它允许避免在将新元素添加到向量中(至少直到不超过保留大小)的情况下,在这些重新分配期间避免不必要的内存重新分配和复制或移动元素。

The third group: 第三组:

Destructor
Destructor

corresponds to the vector vec destructor call at the end of the program. 对应于程序结尾处的向量 vec析构函数调用。

emplace_back matters for "move only" types (like std::unique_ptr). emplace_back对于“仅移动”类型(例如std :: unique_ptr)很重要。

This is wrong and an oversimplification. 这是错误的,并且过于简单。 Not all containers are created equally. 并非所有容器均被平等地创建。 For your vector example, if we use reserve the implementation can do a move assign rather than a construct, eliding our copy/extraneous destructor: 对于您的向量示例,如果我们使用reserve则实现可以执行移动分配而不是构造,从而消除了我们的复制/外部析构函数:

std::vector<T> v;
v.reserve(2);
v.emplace_back(1);
v.emplace_back(1);

Output: 输出:

Constructor
Constructor
---
Destructor
Destructor

For a set: 对于一组:

std::set<T> s;
s.emplace(1);
s.emplace(1);

We get the same output. 我们得到相同的输出。 Why though? 为什么呢 A set should theoretically construct only one object since sets are unique right? 集合在理论上应该只构造一个对象,因为集合是唯一的对吗? In actuality, the typical implementation constructs a temporary node in order to perform the comparison, accounting for the extra construct/destruct even though it never enters the container. 实际上,典型的实现会构造一个临时节点以执行比较,即使它从未进入容器也要考虑额外的构造/破坏。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM