The sad history of the C++ throw(…) exception specifier

Date:September 28, 2018 / year-entry #221
Tags:code
Orig Link:https://blogs.msdn.microsoft.com/oldnewthing/20180928-00/?p=99855
Comments:    14
Summary:I promise not to throw, but who's going to check?

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.


Comments (14)
  1. MarcK4096 says:

    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.

  2. Joshua says:

    The “I can omit unwind code” optimization is the only reason that I care about “throw()” at all.

  3. Alex Guteniev says:

    Standard behavior was there all along, even before C++11.
    But it was available only by using undocumented compiler switch /d1Esrt

  4. Mantas says:

    You could say the MS compiler’s behavior is std::unexpected.

  5. I see that Hitchhiker’s reference. :)

    1. Timothy Byrd says:

      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.

  6. kantos says:

    This is why (among many many reasons) I’ve been moving all my code on windows to /std:latest and /permissive-

  7. Keith P. says:

    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.

  8. Martin says:

    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.

    1. Joshua says:

      It doesn’t work correctly in Java, and it won’t work correctly in C++ either.

    2. 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.

      1. Keith Patrick says:

        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.

      2. Dan says:

        Relevant article on why C# does not have checked exceptions: https://www.artima.com/intv/handcuffs.html

    3. James Curran says:

      > > 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.

Comments are closed.


*DISCLAIMER: I DO NOT OWN THIS CONTENT. If you are the owner and would like it removed, please contact me. The content herein is an archived reproduction of entries from Raymond Chen's "Old New Thing" Blog (most recent link is here). It may have slight formatting modifications for consistency and to improve readability.

WHY DID I DUPLICATE THIS CONTENT HERE? Let me first say this site has never had anything to sell and has never shown ads of any kind. I have nothing monetarily to gain by duplicating content here. Because I had made my own local copy of this content throughout the years, for ease of using tools like grep, I decided to put it online after I discovered some of the original content previously and publicly available, had disappeared approximately early to mid 2019. At the same time, I present the content in an easily accessible theme-agnostic way.

The information provided by Raymond's blog is, for all practical purposes, more authoritative on Windows Development than Microsoft's own MSDN documentation and should be considered supplemental reading to that documentation. The wealth of missing details provided by this blog that Microsoft could not or did not document about Windows over the years is vital enough, many would agree an online "backup" of these details is a necessary endeavor. Specifics include:

<-- Back to Old New Thing Archive Index