std::accumulate能否抛出异常?

3
std::accumulate的C++参考文档没有提到std::accumulate可能抛出任何异常,但其定义中并不包含noexcept。假设使用的类型和操作都不会抛出异常,那么在声明为noexcept的成员函数中使用std::accumulate是安全的吗?还是会导致未定义行为(UB)?
例如:
#include <iostream>
#include <numeric>
#include <vector>

class A {
  std::vector<int> m_v;
public:
  A(std::size_t N) : m_v(N, 1) { }
  int sum() const noexcept { return std::accumulate(m_v.begin(), m_v.end(), 0); }
};

int main()
{
     A x{3};
     std::cout << x.sum() << std::endl;
     return 0;
}

A::sum() 声明为 noexcept 是否会导致未定义的行为?

1
如果一个被声明为 noexcept 的函数抛出异常,那么它的行为是明确定义的。 - Eljay
2个回答

3

std::accumulate() 有前提条件,例如,范围的结束必须可从范围的开始到达。到目前为止,标准库还没有在带有前提条件的函数上放置noexcept(至少,在一般情况下没有;可能会有特殊情况),因为调试实现可以断言存在问题,例如通过抛出异常:当触发未定义行为时会发生什么是未定义的,如果愿意,实现可以自由定义其中任何内容。

此外,std::accumulate() 调用的任何函数都允许抛出异常,即任何 noexcept 声明都需要有条件限制。我认为算法通常不太可能获得相应的 noexcept 声明。

由于规范中未提及在契约内调用 std::accumulate() 抛出异常的任何情况,因此如果没有任何被调用的操作抛出异常,则它将不会抛出异常。


2
@walnut:规范并不保证它不会抛出异常,事实上,唯一的合理解释是资源耗尽,而这在任何时间点都有可能发生。除了计算之外,唯一使用的资源是堆栈空间,也就是说,函数在堆栈溢出时可能会抛出异常。我认为没有其他情况了。 - Dietmar Kühl

2

通常情况下,是可以的。

首先需要注意的是,通过模板参数进行自定义操作的 std::accumulate 可能会抛出异常。

但即使不考虑这点,标准也允许实现在标准库函数没有非抛出异常规定且函数描述没有说明的情况下抛出实现定义的异常,详见 [res.on.exception.handling]/4

话虽如此,在你的示例中使用 std::accumulate 会抛出异常我觉得很惊讶。因为它不需要动态分配内存,所以不太可能抛出 std::bad_alloc,而它是实现定义异常的最有可能候选项。对于 int 型数据的求和、复制和/或移动操作也不会引发异常。

无论如何,在函数添加 noexcept 后如果该函数内部抛出异常,并不会导致未定义的行为。相反,当异常到达 noexcept 函数的外部作用域时,将调用 std::terminate,默认情况下它会终止程序,但可以在一定程度上进行自定义。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接