繁体   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;
}

输出为:

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

为什么有如此多的节制调用? 我看到:

(1)构建临时对象temp1

(2)破坏temp1

(3)构造临时对象temp2

(4)破坏temp2

然后,它被称为temp1和temp 2的复制构造函数或move构造函数。因此,(5)和(6)很清楚。 但是(7)呢?

让我们扩展一下结构:

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";
    }
};

并分别调用push_back方法:

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

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

现在输出看起来更加完整:

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

第一组:

Constructor
Move Constructor
Destructor

对应于第一个push_back调用:

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

输出可能很容易解密:

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

第二组:

Constructor
Move Constructor
Copy Constructor
Destructor
Destructor

对应于第二个push_back调用:

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

稍微复杂一点:

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

在这里您应该记住, 向量类在内部分配其内存,然后对其进行管理以为所有元素提供足够的空间。 因此,如果添加更多元素,则会发生新分配,并且会将旧元素复制或移动到新存储中。

在已知大小的情况下,您可以使用reserve方法,该方法只是为特定数量的元素保留足够的内存。 它允许避免在将新元素添加到向量中(至少直到不超过保留大小)的情况下,在这些重新分配期间避免不必要的内存重新分配和复制或移动元素。

第三组:

Destructor
Destructor

对应于程序结尾处的向量 vec析构函数调用。

emplace_back对于“仅移动”类型(例如std :: unique_ptr)很重要。

这是错误的,并且过于简单。 并非所有容器均被平等地创建。 对于您的向量示例,如果我们使用reserve则实现可以执行移动分配而不是构造,从而消除了我们的复制/外部析构函数:

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

输出:

Constructor
Constructor
---
Destructor
Destructor

对于一组:

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

我们得到相同的输出。 为什么呢 集合在理论上应该只构造一个对象,因为集合是唯一的对吗? 实际上,典型的实现会构造一个临时节点以执行比较,即使它从未进入容器也要考虑额外的构造/破坏。

暂无
暂无

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

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