问题
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 forthrow()
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