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

函数参数抛出异常:代码是否内存泄漏?两种写法有何差异?

关于这段C++代码的内存泄漏问题及两种写法的差异

一、原代码是否会产生内存泄漏?

会存在内存泄漏的风险,原因如下:

在main函数的Foo foo(new A(), new B());调用中,函数实参的求值顺序在C++17及之前的标准中是未指定的。可能出现的执行顺序:

  • 先执行new A()分配内存并构造A对象,得到A*指针;
  • 接着执行new B(),此时B的构造函数抛出std::runtime_error异常;
  • 由于异常抛出时,A*指针还未被MyPtr<A>临时对象接管(MyPtr<A>的构造还未执行),这块内存没有任何智能指针负责释放,最终导致内存泄漏。

当然也存在另一种顺序:先执行new B()抛出异常,此时new A()还未执行,不会产生泄漏。但标准不保证这种顺序,因此代码存在明确的泄漏风险。

另外,MyPtr类本身的实现是基本正确的:移动构造会转移指针所有权并置空原指针,析构函数会释放持有的指针,只要指针被MyPtr接管,就不会泄漏。问题出在参数传递过程中,未被智能指针接管的裸指针因异常丢失。

二、两种写法的差异

Foo foo(new A(), new B()); 和 Foo foo(MyPtr<A>(new A()), MyPtr<B>(new B())); 主要有以下几点差异:

  1. 语法层面

    • 前者依赖隐式转换:编译器自动将A*和B*转换为MyPtr<A>和MyPtr<B>临时对象;
    • 后者是显式构造:直接调用MyPtr的构造函数创建临时对象,意图更清晰,可读性更强。
  2. 内存泄漏风险
    两者的核心风险一致:在C++17之前,都可能因实参求值顺序的不确定性导致内存泄漏。即使是显式写法,编译器仍可能先执行new A(),再执行new B()(抛异常),此时MyPtr<A>的临时对象还未构造,同样会丢失A的内存。

    只有在C17及之后的标准中,函数实参的初始化顺序被规定为从左到右:先完成第一个实参的完整构造(new A() + MyPtr<A>构造),再处理第二个实参。此时如果第二个实参抛出异常,第一个MyPtr<A>临时对象会被析构,释放A的内存,不会泄漏。但这种安全仅针对C17及以后版本。

修复建议

要彻底避免内存泄漏,应该分步骤构造智能指针,确保每一步的裸指针都被立即接管:

try {
    MyPtr<A> a(new A());
    MyPtr<B> b(new B());
    Foo foo(std::move(a), std::move(b));
} catch (const std::exception&) {
}

或者使用标准库的std::unique_ptr替代自定义的MyPtr,配合std::make_unique(C++14及以上),能更安全地避免此类问题:

#include <memory>

// ...

try {
    auto a = std::make_unique<A>();
    auto b = std::make_unique<B>();
    Foo foo(std::move(a), std::move(b));
} catch (const std::exception&) {
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 09:36:25