在 C++ 中,我按noexcept
以下情况标记一个函数:
函数本身不会抛出异常,但是其值类型参数在构造时可能会抛出异常。
下面的代码演示了这种情况:
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_;
// Exceptions are thrown during parameter passing, not inside the function
// Thus it isn't an UB I think.
B(A value = A(true)) noexcept : mem_{std::move(value)} {/* do nothing */}
~B() noexcept = default;
};
在这段代码中,的构造函数B
被标记为noexcept
,默认A(true)
构造函数可能会抛出异常,但我相信这不会导致未定义的行为,因为异常发生在参数传递期间,而不是函数体内。
我的问题是:
在类似情况下,用 标记构造函数和其他函数是否安全noexcept
?这种做法是否可以广泛应用,特别是在函数本身不引发任何异常的情况下?
尝试将异常从
noexcept
函数中传播出去不会导致 UB。它会导致std::terminate
被调用。如果你不想
std::terminate
被调用,那么noexcept
函数接受初始化可能抛出的参数类型仍然是安全的。参见[expr.call]/6在 C++ 中,noexcept 关键字用于声明函数保证不会抛出异常。此保证允许编译器更有效地优化代码,并提供对函数行为的更清晰的预期,尤其是在标准库容器内的移动操作等情况下。
应用 noexcept 时,区分函数体内发生的异常和参数传递期间发生的异常非常重要。noexcept 规范仅适用于函数的内部操作。例如,在提供的代码中,B 的构造函数被标记为 noexcept,这是安全的,因为构造默认参数 A(true) 的任何潜在异常都会在构造函数体执行之前发生,并且不会违反 B 的构造函数本身的 noexcept 保证。
使用 noexcept 的最佳实践包括确保函数内的所有操作在标记为 noexcept 之前均无异常。特别建议对移动构造函数和移动赋值运算符这样做,以增强标准库容器(如std::vector )的性能。此外,在模板中使用条件 noexcept 可以通过允许异常保证依赖于模板参数来提供灵活性。但是,错误地将可能引发异常的函数标记为 noexcept 可能会导致程序通过std::terminate意外终止,因此仔细考虑和彻底测试至关重要。