Why was the old empty throw specification rewritten with a new syntax `noexcept`?

别来无恙 提交于 2019-12-13 20:12:18

问题


The title says it all: why did C++ retire the perfectly satisfying, useful, empty throw specification throw() to replace it with another syntax, with the introduction of the new keyword noexcept?

The empty throw specification is the "only throw these enumerated exceptions guarantee" (written throw(X,Y,Z)), but with zero enumerated exceptions: instead of throwing X, Y or Z (and derived types), you can throw the empty set: that is the guarantee for a function to never throw anything at the caller, in other words, the never throw, or "no throw" specification.

That gratuitously made new code using essentially the same tool, expressing the same promise, incompatible with old code, and old code deprecated and then disallowed, breaking backward compatibility for no apparent reason?

What caused such hate of throw()?

Only the old unsafe gets and the silly useless implicit int were treated as harshly, as far as I can tell.

EDIT:

The alleged "duplicate" is predicated on a false statement.

There is nothing "dynamic" in so called "dynamic exception specification". This is what I hate the most with the new throw specification: the implication of the inane terminology that opposes "dynamic" and "static".


回答1:


That gratuitously made new code using essentially the same tool, expressing the same promise, incompatible with old code

Correction: if a function violates a dynamic exception specification, std::unexpected is called, which invokes the unexpected handler that by default invokes std::terminate. But the handler can be replaced by a user function. If a function violates noexcept, std::terminate is called directly.

The promises are different. throw() meant "may do unexpected things if it tries to emit an exception". noexcept means "will terminate immediately if it tries to emit an exception."

It was only in C++17 when throw() was made exactly equivalent to noexcept. This was after 6 years of exception specifications (including throw()) being deprecated.

It should be noted that the unexpected difference was explicitly cited in the first papers about noexcept. Specifically:

Note that the usefulness of noexcept(true) as an optimization hint goes way beyond the narrow case introduced by N2855. In fact, it goes beyond move construction: when the compiler can detect non-throwing operations with certainty, it can optimize away a great deal of code and/or data that is devoted to exception handling. Some compilers already do that for throw() specifications, but since those incur the overhead of an implicit try/catch block to handle unexpected exceptions, the benefits are limited.

The implicit try/catch block is necessary because unexpected must be called after unwinding the stack to the throw() function. Essentially, every throw() function looks like this:

void func(params) throw()
try
{
  <stuff>
}
catch(...)
{
  std::unexpected();
}

So when an exception tries to leave a throw() function, the exception gets caught and the stack unwinds. More to the point, every throw() function must have exception machinery built into it. So whatever cost a try/catch block incurs will be incurred by every throw() function.

In the first version of noexcept, emitting an exception was straight-up UB, while later versions switched to being std::terminate. But even with that, there is no guarantee of unwinding. So implementations can implement noexcept in a more efficient way. As the system looks up the stack for the nearest catch clause, if it hits the bottom of a noexcept function, it can go straight to terminate without any of the catching machinery.

What caused such hate of throw()?

Correction: not re-purposing a construct does not imply malice. Especially when inventing a new construct would avoid compatibility breakage, as shown above.

It be noted that throw() is considered to be equivalent to a noexcept function in terms of a noexcept expression. That is, calling a throw() function doesn't throw exceptions, and a noexcept(empty_throw()) expression will result in true.

It should also be noted that a new keyword would be needed anyway. Why? Because throw(<stuff>) in C++98/03 already had a meaning. noexcept(<stuff>) has a very different meaning for the <stuff>. Trying to put the noexcept stuff inside of the throw specifier would be... difficult, from a parsing standpoint.

Plus, you could now employ this new keyword as a generalized expression: noexcept(<expression>) resolves to true if none of the calls within it will throw exceptions. This allows you to do conditional logic based on whether things would throw exceptions. You'd need a new keyword to do something like that (or you'd have to make ugly syntax, and C++ has way too much of that going on).



来源:https://stackoverflow.com/questions/58576026/why-was-the-old-empty-throw-specification-rewritten-with-a-new-syntax-noexcept

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!