Short answer to the title question - the idiom to indicate a function can throw is not to document it "this function doesn't throw". That is, everything can throw by default.
C++ is not Java, and doesn't have compiler-checked exceptions. There is nothing in C++ which will allow the compiler to tell you that your code claims it won't throw, but calls something which might. So you can't completely avoid this being a runtime problem. Static analysis tools might help, not sure.
If you only care about MSVC, you could consider using an empty exception specification or __declspec(nothrow)
on functions which don't throw, and throw(...)
on functions which do. This will not result in inefficient code, because MSVC doesn't emit the code to check that functions declared nothrow actually don't throw. GCC can do the same with -fno-enforce-eh-specs
, check your compiler documentation. Everything will then be automatically documented too.
Option 2, the app-wide try-catch isn't really "for safety", it's just because you think you can do something more useful with the exception (like print something out and exit cleanly) than just let the C++ runtime call terminate
. If you're writing code on the assumption that something won't throw, and it actually does, then you may have gone undefined before either one of them actually happens, for example if a destructor makes a false assumption of consistent state.
I'd normally do a variant of (1): for each function document what exception guarantee it offers - nothrow, strong, weak, or none. The last is a bug. The first is prized but rare, and with good coding is only strictly necessary for swap functions. Yes, it's subject to user error, but any means of C++ coding with exceptions is subject to user error. Then on top of that, also do (2) and/or (3) if it helps you enforce (1).
Symbian has a pre-standard dialect of C++, with a mechanism called "leave" which is like exceptions in some respects. The convention in Symbian is that any function which might leave must be named with an L at the end: CreateL
, ConnectL
, etc. On average this reduces user error, because you can see more easily whether you're calling something which might leave. As you might expect, the same people hate it who hate apps Hungarian notation, and if almost all functions leave it ceases to be useful. And as you might expect, if you do write a function which leaves without an L in the name, it can be good long while in the debugger before you figure out the problem, because your assumptions point you away from the actual bug.