noexcept 函数仍然可以调用在 C++17 中抛出的函数吗?
Can a noexcept function still call a function that throws in C++17?
在 P0012R1 中,“使异常规范成为类型系统的一部分”,
我看到 noexcept
现在正在成为函数类型的一部分。
我不知道这是否会阻止 noexcept(true)
函数仍然能够调用 noexcept(false)
函数。
以下代码对 C++17 是否仍然有效?
void will_throw() noexcept(false){
throw 0;
}
void will_not_throw() noexcept(true){
will_throw();
}
在函数类型中包含异常规范与一个函数是否可以调用另一个具有不兼容异常规范的函数(不处理未包含在其异常规范中的异常)是正交的。前者是关于函数指针的类型安全(这样你就不能通过已知不会抛出的函数指针调用抛出函数)。对于后者,我们既可以在编译时禁止它(如Java),也可以将其视为运行时间错误(导致程序终止,如C++当前标准所选择的那样) ).
有人可能会通过从 const
(非静态)成员函数调用非 const
(非静态)成员函数的类比来争论。然而,不同之处在于,在通过 const
成员函数调用的非 const
成员函数中间接修改对象将不会被检测到(否则检测成本太高)并且可能导致严重的错误,这就是为什么在编译期间必须阻止它的原因。鉴于抛出异常的行为是(应该是)一个异常事件,我们可以负担得起插入 运行 时间检查异常是否符合异常规范并应该被释放,或者它是否违反了程序逻辑和应该终止程序。
根据cppreference:
Note that a noexcept
specification on a function is not a compile-time
check; it is merely a method for a programmer to inform the compiler
whether or not a function should throw exceptions.
因此您的代码语法是有效的,但是std::terminate
将在执行时被调用。
noexcept(true)
函数可以调用 noexcept(false)
函数。如果抛出异常,将出现运行时错误。为什么允许这样做的典型示例是:
double hypotenuse(double opposite, double adjacent) noexcept(true)
{
return std::sqrt(opposite*opposite + adjacent*adjacent);
}
std::sqrt
如果它的参数是否定的,将抛出 domain_error
,但显然这永远不会发生在这里。
(在理想情况下,默认情况下会使用 exception_cast
禁止它,以便在需要时允许它。如果抛出异常,结果可能是 UB,或者 std::terminate)。
不幸的是它在编译时有效。
虽然编译器使用noexcept来优化异常处理代码,使代码性能更高,但遗憾的是他们没有进一步推动noexcept 语义上的意思。
理想情况下,当您将方法标记为 noexcept 时,它应该也意味着该方法不应该让任何异常冒泡.因此,如果你有一个标记为 noexcept 的方法,但它调用了其他未标记为 noexcept 的方法,这应该会给你一个编译错误,除非有一个 try/catch 块围绕着任何可以抛出的东西。
简单地调用 std::terminate 是一个非常糟糕的语言设计选择,因为它不会让编写 noexcept 的人承担任何责任 方法。相反,它甚至使消费者无法解决问题,从而损害软件重用。
例如,说我是一个糟糕的库开发者,我写了下面的代码:
我的库附带的头文件 Foo.h:
class Foo
{
public:
void DoSomething() noexcept;
};
您是 FooLib 编写 Bar 应用程序的快乐消费者:
Bar.cpp
#include "Foo.h"
int main()
{
Foo foo;
try
{
foo.DoSomething();
}
catch (...)
{
std::cout << "Yay!" << std::endl;
}
return 0;
}
该代码编译良好并运行良好,直到您让 Foo 抛出异常...如果您将调用包含在 foo.DoSomething() 与 try/catch 块。该代码将简单地中止。
如果您没有 Foo 的代码,您将无法修复它。在这种情况下唯一的解决方案是扔掉 Foo 库并编写自己的库。
Foo.cpp的内容可以是这样的:
static void PotentiallyThrowException()
{
throw 0;
}
void Foo::DoSomething() noexcept
{
PotentiallyThrowException();
}
请注意,由 Foo::DoSomething() 的实现者将他们自己的调用包装到 try/catch 中.但是由于同样的问题,如果他们正在调用其他标记为 noexcept 的方法并且那些开发人员没有这样做,现在是 Foo::DoSomething( ) 即被冲洗。等等等等。
我们可以有把握地说,从语义的角度来看noexcept不仅无用,而且有害。
在 P0012R1 中,“使异常规范成为类型系统的一部分”,
我看到 noexcept
现在正在成为函数类型的一部分。
我不知道这是否会阻止 noexcept(true)
函数仍然能够调用 noexcept(false)
函数。
以下代码对 C++17 是否仍然有效?
void will_throw() noexcept(false){
throw 0;
}
void will_not_throw() noexcept(true){
will_throw();
}
在函数类型中包含异常规范与一个函数是否可以调用另一个具有不兼容异常规范的函数(不处理未包含在其异常规范中的异常)是正交的。前者是关于函数指针的类型安全(这样你就不能通过已知不会抛出的函数指针调用抛出函数)。对于后者,我们既可以在编译时禁止它(如Java),也可以将其视为运行时间错误(导致程序终止,如C++当前标准所选择的那样) ).
有人可能会通过从 const
(非静态)成员函数调用非 const
(非静态)成员函数的类比来争论。然而,不同之处在于,在通过 const
成员函数调用的非 const
成员函数中间接修改对象将不会被检测到(否则检测成本太高)并且可能导致严重的错误,这就是为什么在编译期间必须阻止它的原因。鉴于抛出异常的行为是(应该是)一个异常事件,我们可以负担得起插入 运行 时间检查异常是否符合异常规范并应该被释放,或者它是否违反了程序逻辑和应该终止程序。
根据cppreference:
Note that a
noexcept
specification on a function is not a compile-time check; it is merely a method for a programmer to inform the compiler whether or not a function should throw exceptions.
因此您的代码语法是有效的,但是std::terminate
将在执行时被调用。
noexcept(true)
函数可以调用 noexcept(false)
函数。如果抛出异常,将出现运行时错误。为什么允许这样做的典型示例是:
double hypotenuse(double opposite, double adjacent) noexcept(true)
{
return std::sqrt(opposite*opposite + adjacent*adjacent);
}
std::sqrt
如果它的参数是否定的,将抛出 domain_error
,但显然这永远不会发生在这里。
(在理想情况下,默认情况下会使用 exception_cast
禁止它,以便在需要时允许它。如果抛出异常,结果可能是 UB,或者 std::terminate)。
不幸的是它在编译时有效。
虽然编译器使用noexcept来优化异常处理代码,使代码性能更高,但遗憾的是他们没有进一步推动noexcept 语义上的意思。
理想情况下,当您将方法标记为 noexcept 时,它应该也意味着该方法不应该让任何异常冒泡.因此,如果你有一个标记为 noexcept 的方法,但它调用了其他未标记为 noexcept 的方法,这应该会给你一个编译错误,除非有一个 try/catch 块围绕着任何可以抛出的东西。
简单地调用 std::terminate 是一个非常糟糕的语言设计选择,因为它不会让编写 noexcept 的人承担任何责任 方法。相反,它甚至使消费者无法解决问题,从而损害软件重用。
例如,说我是一个糟糕的库开发者,我写了下面的代码:
我的库附带的头文件 Foo.h:
class Foo
{
public:
void DoSomething() noexcept;
};
您是 FooLib 编写 Bar 应用程序的快乐消费者:
Bar.cpp
#include "Foo.h"
int main()
{
Foo foo;
try
{
foo.DoSomething();
}
catch (...)
{
std::cout << "Yay!" << std::endl;
}
return 0;
}
该代码编译良好并运行良好,直到您让 Foo 抛出异常...如果您将调用包含在 foo.DoSomething() 与 try/catch 块。该代码将简单地中止。
如果您没有 Foo 的代码,您将无法修复它。在这种情况下唯一的解决方案是扔掉 Foo 库并编写自己的库。
Foo.cpp的内容可以是这样的:
static void PotentiallyThrowException()
{
throw 0;
}
void Foo::DoSomething() noexcept
{
PotentiallyThrowException();
}
请注意,由 Foo::DoSomething() 的实现者将他们自己的调用包装到 try/catch 中.但是由于同样的问题,如果他们正在调用其他标记为 noexcept 的方法并且那些开发人员没有这样做,现在是 Foo::DoSomething( ) 即被冲洗。等等等等。
我们可以有把握地说,从语义的角度来看noexcept不仅无用,而且有害。