When exceptions were introduced into the C++ language,
a corresponding throw(...)
dynamic exception
specifier was introduced which annotated which exceptions
could be throw by a function.
// this function might throw an integer or a pointer to char,
// but nothing else.
void foo() throw(int, char*);
This has made a lot of people very angry
and
has been widely regarded as a bad move.
According to the C++98 standard,
if a function throws an exception not listed among
the types specified in its dynamic exception specifier,
the system called the std::unexpected()
function,
and the default behavior of std::unexpected()
is
to terminate the program by calling std::terminate()
.
As a special case, throw()
means that the
function shouldn't throw any exceptions at all.
By C++11, the throw(...)
dynamic exception
specifier was deprecated,
and in C++17, all support for dynamic exception specifiers
was removed save for the
special case of throw()
.
At the same time, they changed the penalty for throwing an exception
when you said you wouldn't:
the runtime calls std::terminate()
directly, rather than passing through
std::unexpected()
.
But of course the Microsoft C++ compiler
has to do things
a little bit differently.
Specifier
C++14 and earlier |
Disallowed exception thrown |
Standard behavior |
Microsoft behavior |
Nonthrowing |
noexcept
noexcept(true) |
std:: unexpected |
std:: unexpected |
throw()
|
std:: unexpected |
undefined behavior ⇐ |
Throwing |
noexcept(false) |
exception propagates |
exception propagates |
throw(something) |
std:: unexpected |
exception propagates ⇐ |
The Microsoft C++ compiler treats the
throw(...)
exception specifier
as a promise on the part of the programmer,
but there is no enforcement.
It trusts you to adhere to your self-imposed contract.
If an exception is thrown when the function promised that no
exceptions would be thrown,
the behavior is undefined.
If the function said that some exceptions could be thrown,
the compiler doesn't validate that the actual thrown exception
is allowed; it just propagates the exception.
In practice, what happened is that the compiler performed optimizations
on the assumption that no disallowed exception would be thrown.
The most common such optimization is
that the compiler won't bother registering unwind codes
for things that it "knows" will never require unwinding
because there are no points where an exception could be thrown
prior to the object's destruction.
void Example()
{
ObjectWithDestructor obj;
obj.stuff_that_does_not_throw();
// destructor runs here
}
If stuff_
that_
does_
not_
throw
is marked as non-throwing, then the compiler
can avoid having to register obj
for unwinding
during exception propagation,
since you promised that
no exception could escape.
And then you throw an exception and invalidate all those
optimizations.
The most common visible effect of this is that
an exception propagated out of a function that should
never have let an exception escape,
and some object destructors failed to run.
But wait, all is not lost.
If you enable /std:c++17
,
then the Microsoft C++ compiler will implement
the standard behavior for throw(...)
.
Specifier
C++17 |
Disallowed exception thrown |
Standard behavior |
Microsoft behavior with /std:c++17 |
Nonthrowing |
noexcept
noexcept(true) |
std:: terminate |
std:: terminate |
throw()
|
std:: terminate |
std:: terminate |
Throwing |
noexcept(false) |
exception propagates |
exception propagates |
throw(something) |
not supported |
not supported |
Yes, it took a long time to get there,
but better late than never.
I love exceptions in C#. But, I never really got into them in C++ since most of my code was dealing with legacy C APIs.
The “I can omit unwind code” optimization is the only reason that I care about “throw()” at all.
Standard behavior was there all along, even before C++11.
But it was available only by using undocumented compiler switch /d1Esrt
You could say the MS compiler’s behavior is std::unexpected.
I see that Hitchhiker’s reference. :)
Raymond knows where his towel is.
In the linked document, it says: “It should also be noted that at least one widely distributed compiler has still not implemented this feature in 2015, and at least one vendor has expressed a desire to never implement the deprecated feature (while that vendor has implemented the noexcept form of exception specification). Code on that platform would not be adversely impacted by the proposed removal, and portable code must always have allowed for the idiosynracies of this platform.”
In hindsight, that doesn’t seems like such a bad move.
This is why (among many many reasons) I’ve been moving all my code on windows to /std:latest and /permissive-
This used to be a big deal on a C++ project I worked on in the 2001 timeframe. We compiled it to Windows, Unix (Solaris, I think), and HP-UX….and we used throw specs religiously. But, bad throw specs caused different problems on the different platforms. I seem to recall Windows not crashing at all, but the Unixes would core dump silently and was a royal pain to debug. I started to attempt to write a stack walker for fixing missing throw specs, but it was a bit out of my league at that time and in that language.
The throw should have been working like in java… the compiler wouldn’t let you compile such a function rather making std terminate if you try to throw or some function saids to throw something that your function isn’t specifying… now the result is that people never knows what is going out.
It doesn’t work correctly in Java, and it won’t work correctly in C++ either.
Checked exceptions in Java are another thing “has made a lot of people very angry and has been widely regarded as a bad move”, up to making people invent bizarre workarounds (SneakyThrows, anyone?) to fool them.
I recall that whole debate on Brad Abrams(?) blog back when .Net was still very young. I was in the camp of liking enforced throw specs (but I also liked C++ pervasive ‘const’ modifier, as annoying as it could be)…it’s not nearly as noisy as, say, Code Contracts.
Relevant article on why C# does not have checked exceptions: https://www.artima.com/intv/handcuffs.html
> > Martin the compiler wouldn’t let you compile such a function
The reason that wouldn’t work (and the main reason the throw() spec exist at all), is the C run-time library. 1000s of function, all legally callable from C++, which should not be able to throw an exception. Further, C function would know nothing about throw specifications, so there’s nothing in its signature/linkage about the throw spec. The throw spec could only appear in a header file to be used only in C++ programs.