C++重载+运算符配合new使用时的对象创建逻辑疑问
问题原理说明
1. 无优化场景下的执行流程
如果编译器完全不做优化,Enemy* baby = new Enemy(one+two); 的执行逻辑如下,会产生3次构造、2次析构,净新增1个存活对象:
- 执行
one + two调用重载的operator+函数:- 函数内部默认构造局部对象
birthed,若你的Enemy::amount静态计数在构造函数中自增,此处计数+1 - 函数返回时,用
birthed拷贝构造一个临时Enemy对象作为返回值,计数再+1,随后birthed析构,计数-1
- 函数内部默认构造局部对象
- 用
operator+返回的临时对象作为参数,调用Enemy的拷贝/移动构造函数,在堆上构造new申请的Enemy对象,计数再+1,随后临时对象析构,计数-1
这种场景下你如果在构造、析构函数中打印日志,可以看到两次中间对象的生成销毁过程,最终仅堆上的对象存活。
2. 实际仅生成1个对象的原因:复制省略优化
C++标准允许编译器跳过不必要的对象拷贝/移动操作,哪怕拷贝/移动构造函数存在修改静态计数这类副作用,优化也是合法的:
- 首先触发具名返回值优化(NRVO):
operator+内部的birthed是与返回值同类型的局部具名对象,编译器可以直接将birthed构造在operator+返回值的内存地址上,省去一次拷贝构造+析构的流程 - 其次触发临时量复制省略(C++17起为强制要求):
operator+返回的临时对象属于纯右值,用来初始化new Enemy的参数时,编译器可以直接把返回对象构造在new申请的堆内存地址上,再省去一次拷贝构造+析构的流程
经过两层优化后,全程只需要构造1个对象,就是你new出来的实例,没有任何中间临时对象生成,所以你统计到的实例数仅增加1。
3. 补充说明
- 如果你想复现你预期的多实例生成过程,可以编译时添加关闭复制省略的参数:GCC/Clang添加
-fno-elide-constructors,MSVC添加/Od关闭优化即可 - 你的当前代码存在内存泄漏:
new出来的Enemy对象没有对应delete操作,push_back到vector里的只是对象的拷贝,堆上的实例永远不会被释放 - 小优化建议:重载
operator+的参数建议修改为const Enemy&,避免意外修改传入对象,同时支持const类型的Enemy实例相加
相关学习资料
可以参考《C++ Primer 第五版》第13章(拷贝控制)、第18章(标准库特殊设施)中关于拷贝/移动构造、值类别、复制省略的相关内容。
内容的提问来源于stack exchange,提问作者user13026362
相关产品推荐
相关产品推荐

