Ale*_*x O 6 c++ stdmap emplace
常见的建议是在几乎所有情况下都优先std::map::try_emplace使用std::map::emplace。
我编写了一个简单的测试来跟踪调用这些函数时的对象创建/复制/移动/销毁,无论是否发生冲突,结果表明,try_emplace当密钥尚未在地图中时,会产生额外的移动和销毁密钥。
为什么行为上有差异?
我确实知道移出对象的移动和销毁通常很便宜,尤其是对于琐碎的键来说,但我仍然对结果感到惊讶,因为它们似乎暗示在某些情况下可能会emplace更有效。
编译器资源管理器链接(Clang 14、libc++、-O3)
来源:
#include <map>
#include <iostream>
struct F {
F(int i): i(i) { std::cout << "- ctor (" << i << ")\n"; }
~F() { std::cout << "- dtor (" << i << ")\n"; }
F(const F& f): i(f.i) { std::cout << "- copy ctor (" << i << ")\n"; }
F(F&& f): i(f.i) { std::cout << "- move ctor (" << i << ")\n"; }
F& operator=(const F& f) { i = f.i; std::cout << "- copy (" << i << ")\n"; return *this; }
F& operator=(F&& f) { i = f.i; std::cout << "- move (" << i << ")\n"; return *this; }
bool operator <(const F& f) const { return i < f.i; }
int i{};
};
int main() {
std::map<F, F> m;
std::cout << "emplace 1:\n";
m.emplace(1, 2);
std::cout << "emplace 2:\n";
m.emplace(1, 3);
std::cout << "clear:\n";
m.clear();
std::cout << "try_emplace 1:\n";
m.try_emplace(1, 2);
std::cout << "try_emplace 2:\n";
m.try_emplace(1, 3);
std::cout << "done:\n";
}
Run Code Online (Sandbox Code Playgroud)
结果:
emplace 1:
- ctor (1)
- ctor (2)
emplace 2:
- ctor (1)
- ctor (3)
- dtor (3)
- dtor (1)
clear:
- dtor (2)
- dtor (1)
try_emplace 1:
- ctor (1)
- move ctor (1)
- ctor (2)
- dtor (1)
try_emplace 2:
- ctor (1)
- dtor (1)
done:
- dtor (2)
- dtor (1)
Run Code Online (Sandbox Code Playgroud)
两个函数的区别在于:
std::map::emplace构造 a value_type,即std::pair就地,然后尝试插入该对。std::map::try_emplace首先尝试查找插入位置,如果找到,它将构造value_type,即std::pair就地。try_emplace似乎为我们节省了一些工作,但请记住,即使我们不使用 向地图中插入任何新内容try_emplace,我们仍然必须检查是否可以以及在哪里插入。默认情况下会发生这种情况std::less,最终会调用:
bool operator<(const F& f) const { return i < f.i; }
Run Code Online (Sandbox Code Playgroud)
F必须存在一个对象才能执行此比较,并且在 期间被构造一次try_emplace。
让我们用正在发生的事情来注释您的示例:
emplace 1:
- ctor (1) // construct node{1, 2}
- ctor (2)
emplace 2:
- ctor (1) // construct node{1, 3}
- ctor (3)
- dtor (3) // can't insert, destroy node{1, 3}
- dtor (1)
clear:
- dtor (2) // destroy node{1, 2}
- dtor (1)
try_emplace 1:
- ctor (1) // construct temporary key
- move_ctor (1) // location found, move key to node
- ctor (2) // construct value
- dtor (1) // destroy temporary key
try_emplace 2:
- ctor (1) // construct temporary key
- dtor (1) // destroy temporary key
done:
- dtor (2) // destroy node{1, 2}
- dtor (1)
Run Code Online (Sandbox Code Playgroud)
我们不能说emplace普遍比 更好或更差try_emplace,而是需要权衡:
| 没有预先存在的密钥 | 插入成功 | 插入失败 |
|---|---|---|
emplace |
零开销1) | 浪费键和值初始化 |
try_emplace |
浪费关键动作 | 浪费密钥初始化 |
try_emplace当我们有一个预先存在的密钥而不是在or内部初始化一个密钥时,权衡就会发生变化emplace:
| 预先存在的密钥 | 插入成功 | 插入失败 |
|---|---|---|
emplace |
浪费关键动作 | 浪费关键移动和初始化,浪费值初始化 |
try_emplace |
浪费关键动作 | 零开销 |
总之,try_emplace与 相比,在最坏的情况下是浪费一招构造函数emplace,在最好的情况下,它绝对更好。在大多数情况下,但不是在所有情况下,更喜欢它。
1) “开销”是指神奇地知道地图中的正确位置(或不存在)并就地初始化节点。预先存在的密钥的初始化被认为是免费的。