函数参数构造可能抛出时,能否安全标记为noexcept?
C++中noexcept标记的安全场景探讨
在C++里,我会在这类场景给函数标记noexcept:函数本身不会抛出异常,但它的值类型参数在构造时可能抛出异常。以下是对应的示例代码:
#include <iostream> #include <stdexcept> #include <utility> struct A { A(bool will_throw) { std::cerr << "Ctor of A\n"; if (will_throw) throw std::runtime_error("An exception from A"); } A(A&& lhs) noexcept { std::cerr << "Move ctor of A\n"; } ~A() noexcept = default; }; struct B { A mem_; // 异常发生在参数传递阶段,而非函数体内 // 我认为这不会导致未定义行为 B(A value = A(true)) noexcept : mem_{std::move(value)} {/* do nothing */} ~B() noexcept = default; };
上述代码中,B的构造函数被标记为noexcept,其默认实参A(true)的构造函数可能抛出异常,但我认为这不会导致未定义行为——因为异常发生在参数传递阶段,而非函数体内。
问题
这类场景下,给构造函数及其他函数标记noexcept是否安全?这种做法能否广泛应用,尤其是在函数自身不会抛出异常的情况中?
解答
该标记是安全且符合C++标准的
noexcept的核心语义是:承诺函数**执行体(包括成员初始化列表)**在运行时不会抛出异常。如果函数体内真的抛出异常,程序会直接调用std::terminate终止。- 但参数(包括默认实参)的初始化属于函数调用的前置阶段,发生在函数入口之前,不属于
noexcept承诺的覆盖范围。此时如果参数构造抛出异常,会正常触发栈展开,不会触发std::terminate,也不会产生未定义行为。 - 以示例代码为例:调用
B()时,默认实参A(true)的构造抛出异常,这个异常会直接被调用方的异常处理逻辑捕获,根本不会进入B的构造函数体,因此noexcept的承诺没有被违反。
这种做法可以广泛应用,但要明确边界
- 只要函数自身的逻辑(初始化列表、函数体内部)确实不会抛出异常,就可以放心标记
noexcept,完全不用考虑参数是否会在构造时抛出异常。 - 需要注意区分:如果函数内部会主动构造参数(比如在函数体内创建某个值类型对象),那就要重新评估该操作是否可能抛出;但如果参数是在调用前就完成构造(包括默认实参的初始化),就不会影响
noexcept的有效性。
- 只要函数自身的逻辑(初始化列表、函数体内部)确实不会抛出异常,就可以放心标记
避免滥用的注意事项
- 绝对不能给内部存在可能抛出异常操作的函数标记
noexcept:比如函数体内调用了非noexcept的函数、存在可能失败的动态内存分配、或者有未处理的异常抛出路径,此时标记noexcept会导致异常触发时直接终止程序,这通常是不符合预期的。 - 默认实参的异常处理责任在调用方:
noexcept标记不会改变调用方需要处理参数初始化异常的责任,调用方仍需意识到默认实参可能抛出异常,并做好相应的捕获逻辑。
- 绝对不能给内部存在可能抛出异常操作的函数标记
内容的提问来源于stack exchange,提问作者Konvt
相关产品推荐
相关产品推荐

