Larry Osterman's WebLog

Confessions of an Old Fogey
Blog - Title

Why add a throw() to your methods?

Why add a throw() to your methods?

Rate This
  • Comments 19

Towards the end of the comments in my last "What's wrong with this code" , hippietim asked why I added a throw()attribute to the destructor of the CCoInitializer.  The answer's pretty simple.  If you add a throw() attribute around routines that never throw, the compiler can be clever about code motion and optimization.  Consider the following totally trivial:

class MyClass
{
    size_t CalculateFoo()
    {
        :
        :
    };
    size_t MethodThatCannotThrow()
throw()
    {
       
return 100;
    };
   
void ExampleMethod()
    {
        size_t foo, bar;
        try
        {
            foo = CalculateFoo();
            bar = foo * 100;
            MethodThatCannotThrow();
            printf(
"bar is %d", bar);
        }
        catch (...)
        {
        }
    }
};

 

When the compiler sees this, with the "throw()" attribute, the compiler can completely optimize the "bar" variable away, because it knows that there is no way for an exception to be thrown from MethodThatCannotThrow().  Without the throw() attribute, the compiler has to create the "bar" variable, because if MethodThatCannotThrow throws an exception, the exception handler may/will depend on the value of the bar variable.

In addition, source code analysis tools like prefast can (and will) use the throw() annotation to improve their error detection capabilities - for example, if you have a try/catch and all the functions you call are marked as throw(), you don't need the try/catch (yes, this has a problem if you later call a function that could throw).

  • Except when did VC++ actually start paying attention to throw declarations.  I haven't checked in 2005 but I know earlier versions output a compile warning about ignoring the throw() declaration.

    -Jeff
  • You're right, VC doesn't do much with a throw declaration. Except since VC7 (I think, 7.1 does it for sure) it will treat throw() as __declspec(nothrow).

    So it ignores exception specifications, except when they're empty. Personally, I don't use throw() either, because VC still doesn't follow standard behaviour. If a function marked throw() does throw, the C++ runtime is supposed to call unexpected(), which by default tries to rethrow as std::bad_exception and if that fails (which it will with an empty throw specification), call abort().

    VC will never call unexpected() because it doesn't properly support exception specifications, and since it optimizes away all exception handling code when you specify throw(), I'm not exactly sure what will happen if an exception is thrown, but it can't be good.

    (PS: I think exception specifications in C++ are completely useless due to the way they're done, so I don't blame the VC team for not wanting to waste time implementing it)
  • Visual C++ doesn't handle throw(type) "properly" - it's the same as throw(...) which is the same as not putting anything there. It does handle throw() as Larry says, though.

    It's still not "correct" as specified in the standard (which says that it should call unexpected() if you throw an exception from a method marked throw()) but it does do the optimizations. It's the same as marking the method with __declspec(nothrow).
  • Since at least VC6, throw() specification (with empty type list) is treated as an indicator that the function does not throw. Any other form of exception specification is ignored.

    Note that VC does not implement unexpected() semantics. If a function marked throw() does throw an exception after all, bad things happen.

    Further, it is expected that the compiler will implement exception specifications in full at some point. When this happens, throw() marker will become a pessimization in some (many, most?) cases - while the compiler may be able to omit exception stack frame at the call site, it is forced to enclose the body of the function into an implicit try/catch. To avoid a performance hit when porting to a future compiler version, it is recommended that one uses __declspec(nothrow) instead. __declspec(nothrow) is and will continue to be equivalent to existing (non-standard, eventually to be removed) throw() semantics.
  • Isn't it forbidden to throw stuff from destructors anyway, though?
  • And wait -- if it doesn't call unexpected(), but optimises code expecting things to not throw, isn't that the worst of all possible worlds?  If something marked as throw() does actually throw (through a coding error, or unanticipated exception from further down), you'll end up with undefined behaviour.
  • I don't think any C++ implementation implements "checked exceptions", do they?  Everything I've seen on checked exceptions suggest they have undesired side-effects and don't do what was intended--despite being well intended.  I think the general consensus is exception specifications are "incorrect".

    Larry's example is somewhat academic; if you have the forethought to declare your function as not throwing exceptions, you should have the same forethought to delete variables that are never read.
  • C++ exception specifications were certainly implemented by some compilers as early as 1995, but one big discovery was that they actually reduced performance in a lot of cases. throw() may be the exception to this, but you do not ever want to use throw(Type).
  • It seems that the __declspec(nothrow) syntax would be preferred rather that throw().  Back in The Day (TM), we looked at doing this, and came up with the following....

    //
    //  NOTHROW is used as a hint to the compiler that the declared function
    //  doesn't throw exceptions.  This allows the compiler to omit stack unwind
    //  code for objects that have no throwable functions during their lifetime.
    //
    //  Obviously, this should *not* be used except where the function really
    //  doesn't throw exceptions.  This means that if a function explicitly or
    //  *implicitly* calls a function that may throw, then that function should not
    //  be marked as 'NOTHROW'.
    //
    // Common *implicit* calls are:
    //   -- Constructors of base or contained objects
    //   -- Asignment operators
    //   -- Conversion operators and copy constructors
    //   -- Destructors of base or contained objects.
    //
    // Templates are a special case.  In most cases, any template function that
    // makes direct use of the template type should *not* be tagged as 'NOTHROW'.
    // You must assume that any method of a template type can throw, including
    // construction and assignment.
    //
    // 'NOTHROW' is preferred to the 'throw()' syntax, for two reasons:
    //   -- Current MS compilers do not support the full 'throw()' syntax.  In
    //      particular, anthing other than empty parentheses is ignored, and the
    //      C++ Standard sematics of calling 'unexpected()' when an exception is
    //      thrown is not implemented.
    //   -- 'throw()' is specified on the *definition*, not the declaration.
    //      Unless the function is defined inline, no other compiland will be able
    //      to use the hint that the function doesn't throw exceptions.
    //
    #define NOTHROW __declspec(nothrow)
  • Scott Meyers' "Effective C++" claims that adding throw() generates extra function overhead to check (at runtime!) that your function really did not throw... and if it does throw, then it calls terminate() for you.
  • Here is a more or less authoritative explanation by one of the VC architects and a very prominent member of C++ community

    http://www.gotw.ca/publications/mill22.htm

    The summary:
    Moral #1: Never write an exception specification.
    Moral #2: Except possibly an empty one, but if I were you I’d avoid even that.

  • As Dean noted, on a compiler that handles the exception handling specification correctly, throw() is actually a pessimization, as it is asking the compiler to emit runtime code to *enforce* that exceptions are not thrown out of the function. IMO, exception specifications were goofed in C++; they should have been advisory hints, with the optimizer allowed to take advantage of them and undefined behavior invoked if violated, but the compiler should not have been obliged to check for violations at runtime.

    throw() isn't very useful on an inline function, btw, since the optimizer can determine the lack of EH paths on its own. Also, as far as I can tell, current versions of Visual C++ are unable to take advantage of throw() or __declspec(nothrow) on a virtual method, which is unfortunate.

    The advantages of VC++'s throw() are not that academic: performance-wise it won't gain you much besides allowing more tail calls, but on X86 it can gain you as much as a 10% reduction in code size. There are lots of places where it could be applied en masse if it were effective on virtual methods, such as COM interfaces.
  • GCC implements checked exceptions, but C++ checked exceptions aren't like Java checked exceptions. In C++, checked exceptions are checked at run-time, not compile-time. In fact, the following code:

    void myfunc() throw (a,b)
    {
     //...
    }

    Is (almost) exactly equivalent to:

    void myfunc()
    {
     try
     {
       // ...
     }
     catch (a) { throw; }
     catch (b) { throw; }
     catch (...) { std::unexpected(); }
    }

    Which is why many people believe checked exceptions in C++ to be pretty useless, and I can understand why VC++ decided not to implement them.
  • If VC's implementation of throw() is A.) non-standard and B.) just like __declspec(nothrow), then I'd prefer just using __declspec(nothrow) rather than something that *might* emit different code in the future if VC ever changed its implementation of throw() vis-a-vis the standard.
  • Miral:  Throwing from a destructor is not forbidden by the language, but it's an exceedingly bad idea that should be avoided at all costs.
Page 1 of 2 (19 items) 12