std::map::try_emplace完全详解

std::map::try_emplaceC++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 已存在,什么都不做,只返回迭代器。

  • 关键点

    1. 避免不必要的拷贝/移动:只有在插入时才会真正构造 value
    2. insert 的区别insert 需要完整的 <key, value> 对;而 try_emplace 只需要 key + value 的构造参数。
    3. 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;

    // 用参数直接构造 vector
    m.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. 小坑提醒

  1. key 的构造开销try_emplace 仍需构造/移动 key(除非用 hint 且 key 已存在)。
  2. operator[] 区别map[key] 会在 key 不存在时默认构造 value,可能导致性能浪费;而 try_emplace 可以传参数构造更高效。
  3. hint 仅是优化:给错 hint 不会错,只是回退到普通查找。
  4. 返回值处理:如果你只关心是否插入,可以忽略迭代器;如果要访问元素,记得用 it->second

总结一句话就是
try_emplacemap / 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 已存在,不会构造 C
    cache.try_emplace(2, "D");  
    // key=2 不存在,才构造 D

    std::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 存在时会有不必要的构造。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

点云SLAM

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值