题目

编写程序,演示 C++17 引入的 std::map 高效原语:try_emplace 与 insert_or_assign。通过构造开销昂贵的对象,证明 try_emplace 在键已经存在时完全不会构造临时对象(彻底优于 insert 与 emplace);并展示 insert_or_assign 如何以原子语义优雅替代传统的 m[key] = val,同时清晰返回是否发生了新插入还是就地覆盖更新的布尔标志。

输入格式:无需外部输入。

输出格式:输出 try_emplace 与 insert_or_assign 执行过程中的构造调用日志与状态返回值。

数据范围与约定:C++17 现代节点操作与高效就地语义。

思路与算法

1. emplace 的历史遗憾
C++11 的 map.emplace(k, args...) 即使在 k 已经存在导致插入失败的情况下,也会提前在栈上把 args... 构造出临时 Value 对象,造成无意义的昂贵构造与析构。

2. try_emplace 真正的惰性原地构造
try_emplace(k, args...) 在检测到 k 已经存在时,绝不触碰也不构造 Value 的参数包,实现绝对的零无用开销。

3. insert_or_assign 的清晰契约
相比 operator[] 需要 Value 支持默认构造,insert_or_assign 无需默认构造,直接接收构造好的 Value;返回值是 std::pair<iterator, bool>,布尔为 true 表示新插入,false 表示已存在并成功覆盖。键已存在时走的是赋值而不是析构节点再重建:本例 HeavyObject 声明了析构函数因而抑制隐式移动,赋值拷贝字符串后析构的是临时对象 Updated-Payload,节点里的对象不会先打出 First-Payload 的析构日志。

完整程序

#include <iostream>
#include <map>
#include <string>

class HeavyObject {
public:
    std::string data;
    HeavyObject(std::string d) : data(std::move(d)) {
        std::cout << "    [构造 HeavyObject] \"" << data << "\"\n";
    }
    ~HeavyObject() {
        std::cout << "    [析构 HeavyObject] \"" << data << "\"\n";
    }
};

int main() {
    std::map<int, HeavyObject> myMap;

    std::cout << "=== 1. 使用 try_emplace 首次插入 Key 1 ===\n";
    auto [it1, ok1] = myMap.try_emplace(1, "First-Payload");
    std::cout << "插入结论: " << (ok1 ? "成功新建" : "失败") << "\n\n";

    std::cout << "=== 2. 对已存在的 Key 1 再次调用 try_emplace ===\n";
    // 关键:因为 Key 1 已存在,参数 "Ignored-Payload" 根本不会被用于构造 HeavyObject!
    auto [it2, ok2] = myMap.try_emplace(1, "Ignored-Payload");
    std::cout << "插入结论: " << (ok2 ? "成功新建" : "已存在,短路跳过构造") 
              << ", 当前数据依然为: " << it2->second.data << "\n\n";

    std::cout << "=== 3. 使用 insert_or_assign 覆盖更新 Key 1 ===\n";
    auto [it3, ok3] = myMap.insert_or_assign(1, HeavyObject("Updated-Payload"));
    std::cout << "操作结论: " << (ok3 ? "新键插入" : "旧键覆盖更新") 
              << ", 当前数据已变为: " << it3->second.data << "\n\n";

    std::cout << "=== 4. 程序结束容器销毁 ===\n";
    return 0;
}

运行示例

输出:

=== 1. 使用 try_emplace 首次插入 Key 1 ===
    [构造 HeavyObject] "First-Payload"
插入结论: 成功新建

=== 2. 对已存在的 Key 1 再次调用 try_emplace ===
插入结论: 已存在,短路跳过构造, 当前数据依然为: First-Payload

=== 3. 使用 insert_or_assign 覆盖更新 Key 1 ===
    [构造 HeavyObject] "Updated-Payload"
    [析构 HeavyObject] "Updated-Payload"
操作结论: 旧键覆盖更新, 当前数据已变为: Updated-Payload

=== 4. 程序结束容器销毁 ===
    [析构 HeavyObject] "Updated-Payload"