std::map::try_emplace完全详解
std::map::try_emplace
是 C++17 新增的关联容器成员函数,主要目标是优化 map
/unordered_map
插入的性能与安全性。
1. 函数原型
template< class... Args >
std::pair<iterator,bool> try_emplace(const key_type& k, Args&&... args );template< class... Args >
std::pair<iterator,bool> try_emplace(key_type&& k, Args&&... args );template< class... Args >
iterator try_emplace(const_iterator hint, const key_type& k, Args&&... args );template< class... Args >
iterator try_emplace(const_iterator hint, key_type&& k, Args&&... args );
2. 作用与特点
-
功能:如果 key 不存在,就原地构造一个 value(用
args...
调用mapped_type
的构造函数);如果 key 已存在,什么都不做,只返回迭代器。 -
关键点:
- 避免不必要的拷贝/移动:只有在插入时才会真正构造
value
。 - 与
insert
的区别:insert
需要完整的<key, value>
对;而try_emplace
只需要 key + value 的构造参数。 - 与
emplace
的区别:emplace
在 key 存在时仍会计算参数并构造临时对象,然后发现 key 已存在再丢弃;而try_emplace
在 key 存在时不会触发 value 构造。
- 避免不必要的拷贝/移动:只有在插入时才会真正构造
3. 返回值
-
版本 1 / 2:返回
std::pair<iterator,bool>
first
:指向新插入元素或已存在元素second
:true 表示插入成功,false 表示 key 已存在
-
带 hint 版本:返回
iterator
(效率优化,若 hint 合理可加速查找)
4. 示例讲解
示例 1:基础用法
#include <map>
#include <string>
#include <iostream>int main() {std::map<int, std::string> m;m.try_emplace(1, "SLAM");m.try_emplace(2, 5, 'x'); // 用 string(size, char) 构造auto [it, inserted] = m.try_emplace(1, "will_not_happen");std::cout << inserted << " " << it->second << "\n"; // 0 SLAM
}
如果 key 已存在(如 1
),不会新建 string,也不会覆盖旧值。
示例 2:避免不必要的构造
#include <map>
#include <string>
#include <iostream>struct BigObj {BigObj(std::string s) { std::cout << "constructing " << s << "\n"; }
};int main() {std::map<int, BigObj> m;m.try_emplace(1, "A"); // 构造 BigObj("A")m.try_emplace(1, "B"); // key 存在,不会构造 BigObj("B")!
}
如果用 emplace
,即使 key 存在,BigObj("B")
也会被构造一次再丢掉;try_emplace
避免了这笔浪费。
示例 3:完美转发构造 value
#include <map>
#include <vector>
#include <iostream>int main() {std::map<int, std::vector<int>> m;// 用参数直接构造 vectorm.try_emplace(1, 5, 10); // 构造 vector<int>(5, 10)for (auto x : m[1]) std::cout << x << " "; // 10 10 10 10 10
}
示例 4:与 hint 结合
#include <map>
#include <string>
#include <iostream>int main() {std::map<int, std::string> m;auto it = m.begin();// 提供 hint,可能优化性能it = m.try_emplace(it, 42, "hello");std::cout << it->first << " => " << it->second << "\n";
}
5. insert
/ emplace
/ try_emplace
/ insert_or_assign
对比
函数 | 行为 | key 存在时 | value 构造时机 |
---|---|---|---|
insert({k, v}) | 插入完整 pair | 不插入 | 总会先构造 v |
emplace(k, args...) | 原地构造 value | 不插入 | 总会构造一次(即使 key 存在) |
try_emplace(k, args...) | 原地构造 value | 不插入 | 仅在 key 不存在时才构造 |
insert_or_assign(k, v) | 插入或覆盖 | 覆盖 value | 总会构造 v |
6. 适用场景
- 懒加载:只在 key 首次出现时才创建复杂对象(如点云、图优化的残差结构)。
- 避免浪费:value 构造代价昂贵(如大数组、外部资源),
try_emplace
可以省去无效构造。 - 与工厂模式结合:存储大对象时,可以延迟构造到需要时。
- 读多写少的缓存:插入时才构造,命中时只返回已有对象。
7. 小坑提醒
- key 的构造开销:
try_emplace
仍需构造/移动key
(除非用 hint 且 key 已存在)。 - 与
operator[]
区别:map[key]
会在 key 不存在时默认构造 value,可能导致性能浪费;而try_emplace
可以传参数构造更高效。 - hint 仅是优化:给错 hint 不会错,只是回退到普通查找。
- 返回值处理:如果你只关心是否插入,可以忽略迭代器;如果要访问元素,记得用
it->second
。
总结一句话就是:
try_emplace
是 map
/ unordered_map
最优的延迟构造插入方法,能避免重复构造 value,特别适合 大对象存储、昂贵构造的容器。
8.综合应用示例
下面是一个 综合示例,把 insert
/ emplace
/ try_emplace
/ insert_or_assign
全部放在一起对比,结合一个“昂贵构造对象”的场景,直观展示它们在 性能、行为、覆盖策略 上的不同。
综合示例:地图缓存系统
在点云 SLAM 地图缓存中,每个 Key 表示一个分区 ID,每个 Value 是一个“昂贵的点云对象”。
#include <map>
#include <string>
#include <iostream>// 模拟一个昂贵对象(构造时输出日志)
struct BigPointCloud {BigPointCloud(std::string name) { std::cout << "Constructing: " << name << "\n"; }BigPointCloud(const BigPointCloud& other) {std::cout << "Copying: " << "\n";}BigPointCloud(BigPointCloud&& other) noexcept {std::cout << "Moving: " << "\n";}
};int main() {std::map<int, BigPointCloud> cache;std::cout << "\n=== insert ===\n";cache.insert({1, BigPointCloud("A")}); // 总会先构造 A,再决定是否插入std::cout << "\n=== emplace ===\n";cache.emplace(1, "B"); // 即使 key=1 已存在,也会构造 B,然后丢掉std::cout << "\n=== try_emplace ===\n";cache.try_emplace(1, "C"); // key=1 已存在,不会构造 Ccache.try_emplace(2, "D"); // key=2 不存在,才构造 Dstd::cout << "\n=== insert_or_assign ===\n";cache.insert_or_assign(2, BigPointCloud("E")); // 覆盖已有 key=2 的值,构造 E
}
输出示例
=== insert ===
Constructing: A=== emplace ===
Constructing: B=== try_emplace ===
Constructing: D=== insert_or_assign ===
Constructing: E
行为总结
- insert:总会先构造 A(即使 key=1 已存在也会构造)
- emplace:key=1 已存在,但仍然构造了 B(浪费)
- try_emplace:key=1 已存在,不构造 C;key=2 不存在,才构造 D
- insert_or_assign:key=2 已存在,强制覆盖,构造 E
从这个综合示例就能看出:
- 如果只想在 key 不存在时插入,且避免浪费构造:
try_emplace
是首选。 - 如果你想要 存在时覆盖,那就用
insert_or_assign
。 - 如果你用
insert
/emplace
,在 key 存在时会有不必要的构造。