You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++全局std::map存储自定义对象时析构函数提前调用问题

析构函数提前调用的原因

你观察到的insert阶段触发的析构函数,根本不是全局map内部存储对象的析构,而是插入流程中产生的多个临时对象生命周期结束触发的析构,属于完全正常的行为。

插入流程的对象生命周期拆解

你写的插入代码:

Test::g_tMap.insert(std::pair<Test::ETestMapKeyId,TestMapObject>(Test::k_ETestMapKeyFirst,TestMapObject("Alice")));

单这一行执行时,会按顺序产生3个TestMapObject实例:

  • 第1个:TestMapObject("Alice") 直接构造的栈临时对象,会触发你写的普通构造函数,打印第一行Constructor called
  • 第2个:构造std::pair时,会把上面的临时对象拷贝进pair内部,这里调用的是编译器自动生成的默认拷贝构造函数,你没有加日志,所以看不到这次构造的输出
  • 第3个:insert执行时,会把整个临时pair拷贝到map的红黑树存储节点中,连带把pair内的TestMapObject再拷贝一份到map的内部内存,这次调用的还是默认拷贝构造,同样没有日志输出

这行代码执行完后,前两个临时对象(栈上的TestMapObject("Alice")、栈上的临时pair内的TestMapObject成员)生命周期结束,会依次触发两次析构,对应你看到的前两行Destructor called。
第二次插入"Mocha"的流程完全一致,会再触发1次普通构造、2次临时对象析构,加起来刚好匹配你看到的输出:

Constructor called
Destructor called
Destructor called
Constructor called
Destructor called
Destructor called
 are we destructed? 

为什么后续还能正常读取对象

map内部真正存储的两个TestMapObject实例,是两次拷贝后存到map自身内存里的对象,只要map本身不被销毁、对应键值不被删除,这两个对象就一直有效。你通过GetMapObjectById拿到的就是指向这两个合法对象的指针,后续无限循环打印名称是完全合法的行为,不属于未定义操作。
你之所以会误以为map内的对象被析构,是因为没有手动实现拷贝构造函数,漏掉了两次拷贝构造的日志,才会出现“构造次数和析构次数对不上、对象析构了还能访问”的错觉。

验证与优化方式

你可以给类补充拷贝构造函数加日志,就能看到完整的对象生命周期流转:

TestMapObject(const TestMapObject& other) {
    std::cout << "Copy constructor called" << std::endl;
    m_sName = other.m_sName;
}

重新运行后你会发现,所有构造(普通构造+拷贝构造)的次数和析构次数完全匹配,map内存储的对象直到程序退出前都不会触发析构。

如果想避免插入时的多余临时对象拷贝,可以改用emplace方法直接在map内部原地构造对象,减少不必要的性能开销:

Test::g_tMap.emplace(Test::k_ETestMapKeyFirst, "Alice");
Test::g_tMap.emplace(Test::k_ETestMapKeySecond, "Mocha");

这种写法只会在map内部存储位置构造一次对象,插入阶段不会产生多余的临时对象,也就不会出现插入完成前就触发析构的日志了。


内容的提问来源于stack exchange,提问作者Kayla

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 12:36:21