【发布时间】:2016-01-15 05:30:05
【问题描述】:
我最近在几种情况下遇到过这个问题,对此表达的一些意见让我感到惊讶。这是第一个简单的例子:
void f(std::vector<double> x) {};
问题是:是否可以记录或描述f 提供不抛出保证?同样,我怀疑由于异常不是从 f 的主体生成的,因此使用 noexcept 在技术上是合乎情理的。但是应该标记它noexcept 吗?例如,set 的优化版本以某种方式发现添加模板化比较器不得抛出的要求很有用。它在编译时使用静态断言检测到这一点并导致错误。然而,有人可以为取值的向量编写一个比较器,并将其标记为 noexcept,并将其与此版本的 set 一起使用。如果这会导致不良行为,那是容器作者的错吗?还是标记比较器noexcept的人?
再举一个涉及另一种异常保证的例子,考虑:
void g(std::vector<double> x, std::unique_ptr<int> y);
这个功能能否提供强有力的保证?
std::vector<double> p{1.0, 2.0};
auto q = std::make_unique<int>(0);
bool func_successful = true;
try {
g(p, std::move(q));
}
catch (...) {
func_successful = false;
}
if (!func_successful)
assert(q);
我会争辩说,如果断言可能失败,那么 g 不提供强有力的保证,因为在调用 g 之前q 不是空的(记住std::move 实际上并没有移动任何东西)。它可能会失败:参数评估的顺序未指定,因此可以先构造 y 清空 q,然后构造 x 并抛出。即使它在技术上不是发生在体内,而是在调用函数的行为中,函数是否仍然对此负责?
编辑:我要提到 Herb Sutter 在这里谈论这个问题:https://youtu.be/xnqTKD8uD64?t=1h11m56s。他说标记这样的函数 noexcept 是“有问题的”;希望得到更详细的答复。
【问题讨论】:
-
我现在找不到文本,但是函数异常规范肯定只有在输入函数体后才开始。调用者可以控制参数的行为,例如我希望
noexcept(f(vec))是false但noexcept(f(std::move(vec)))是true。 -
set的例子很糟糕。你会测试像noexcept(comp(a, b))这样的东西,这已经考虑到了复制。 -
@M.M 我同意。问题是它是否应该在精神上被标记为 noexcept,或者这是否只是利用一种边缘情况。调用者可以控制参数的行为,但是 noexcept 函数不应该与任何可以调用的参数一起抛出,对吧?
-
@T.C.那是个很好的观点。我手边没有更好的例子,但我肯定全神贯注。我认为尽管可以看到可能存在更好的示例。
-
我不认为这就是它的意思。
noexcept对f仍然有用,因为调用代码仍然可以基于此进行优化,即使调用代码准备捕获构造参数时发生的异常。 (它知道不会有任何其他例外)
标签: c++