23

Today I learned that swap is not allowed to throw an exception in C++.

I also know that the following cannot throw exceptions either:

  • Destructors
  • Reading/writing primitive types

Are there any others?
Or perhaps, is there some sort of list that mentions everything that may not throw?
(Something more succinct than the standard itself, obviously.)

curiousguy
  • 8,038
  • 2
  • 40
  • 58
user541686
  • 205,094
  • 128
  • 528
  • 886
  • 1 + 1 comes to mind. Seriously, why do you ask? Seems rather unimportant if you ask me. – Richard J. Ross III Jul 23 '12 at 03:27
  • 10
    @RichardJ.RossIII: Unimportant?! Pardon me, but LOL!! It's the *foundation* of strong exception safety in C++! – user541686 Jul 23 '12 at 03:27
  • @Mehrdad while that is true, many projects decide to disable/not use exceptions entirely, in which case nothing must throw an exception, so it is in many cases, an arbitrarily defined set, and otherwise might be highly dependant on what libraries you are using and what policies they define. Im still interested in seeing a response though. – Preet Kukreti Jul 23 '12 at 03:39
  • 2
    I'd say move constructors would do well to not throw an exception, since you get some very nasty problems in standard containers if they do. – Xeo Jul 23 '12 at 03:41
  • 2
    @Preet: Except that disabling exceptions is dumb, IMHO. What do you get from that? Unsafe C-style error handling? Right, like anyones likes those... – Xeo Jul 23 '12 at 03:42
  • 4
    To be pedantic C++ allows destructors to throw, it's just C++ programmers that don't, because they don't like `std::terminate`. – R. Martinho Fernandes Jul 23 '12 at 03:42
  • 1
    @Xeo I'm not saying its smart or dumb, but it is a fact that many projects/companies dont use exceptions. For one, using exceptions adds a definite performance overhead. There are quite a few experts in the industry who prefer avoiding exceptions (and think of it like a goto), you will find plenty of examples if you Google it. – Preet Kukreti Jul 23 '12 at 03:44
  • 5
    @Preet: It's not a "definite performance overhead". AFAIK, GCC and Clang use the zero-overhead model that consumes more memory but has no overhead at runtime. – Xeo Jul 23 '12 at 03:45
  • 1
    @Xeo even in those cases, using more memory can potentially reduce cache coherency and hence result in lower performance as a result of more frequent cache misses depending on access patterns. FYI, im not promoting this view as the correct one. I'm sure there are cases where either enabling or disabling exceptions result in net gains depending on the goals of the project. Maybe you are writing a 4k demoscene demo? Maybe a robust enterprise app? who knows. – Preet Kukreti Jul 23 '12 at 03:47
  • 3
    @PreetKukreti Only if an exception is actually thrown. And since exceptions get thrown only in _exceptional_ circumstances, the net overhead is pretty much irrelevant. – Etienne de Martel Jul 23 '12 at 03:47
  • 2
    Oh yeah, on a pedantic note, `swap` is allowed to throw. It's `noexcept` depends on whether the type `T` ´is_nothrow_move_constructible` and `is_nothrow_move_assignable`. – Xeo Jul 23 '12 at 03:58
  • @EtiennedeMartel Unless the programmer is abusing exceptions as a form of message-passing service (which is not uncommon), in which case you will see exceptions being thrown in non-exceptional cases. – Preet Kukreti Jul 23 '12 at 04:02
  • 2
    @PreetKukreti: The exceptions debate is a decade old, and done: exceptions are faster and cleaner compared to correct error code handling. But yes, not everyone has the *luxury* of using them. – GManNickG Jul 23 '12 at 04:03
  • 1
    @GManNickG I use exceptions myself. But all the comments here are assuming that exceptions are all "pro" and no "con". Just because gotos are dangerous, it does not mean they are automatically **always** bad. Its easy but incorrect to categorize everything as either "always good" or "always bad". There are some non-trivial complexity issues surrounding exceptions and some cases, where depending on the context of a project may make more sense to be disabled entirely. I was just pointing out that these cases exist. With this attitude im surprised you all arent using a managed language instead. – Preet Kukreti Jul 23 '12 at 04:09
  • 4
    @PreetKukreti: I don't see such comments. We're adults, I think, and don't need to be constantly reminded that every rule has a domain. In any domain most people will be using, just use exceptions. If you're not in that domain, you already know it, so what benefit is there in bringing it up? – GManNickG Jul 23 '12 at 04:21
  • @GManNickG I think that in a circumstance where someone is looking for a broad and "universal" rule (that presumably would be applied everywhere), it is perfectly valid and prudent to point out exceptional domains. Just because the domain is not applicable to you, it does not mean that it is irrelevant or provides no value. The perspective of promoting a "common" preference to a law is ultimately, I think, a damaging one. – Preet Kukreti Jul 23 '12 at 04:50
  • 1
    @PreetKukreti Oh, sure, exceptions used in an incredibly wrong way are wrong indeed. No need to tell me that. – Etienne de Martel Jul 23 '12 at 14:01
  • @EtiennedeMartel you said "exceptions get thrown **only** in exceptional circumstances". I agree that that is the purpose/design, but I was pointing out that it may not be the reality. Just because you have a green light at an intersection, it doesn't mean you wont get hit by a car. The way you stated it was that exceptions are only ever thrown in exceptional cases. While that is the intent, it is not always the reality. Anyway, I really dont know why everyone is showing such hostility. I am not trying to attack your seemingly safe and happy mental models nor am I saying exceptions are bad. – Preet Kukreti Jul 23 '12 at 18:02

3 Answers3

17

There is a great difference between cannot and should not. Operations on primitive types cannot throw, as many functions and member functions, including many operations in the standard library and/or many other libraries.

Now on the should not, you can include destructors and swap. Depending on how you implement them, they can actually throw, but you should avoid having destructors that throw, and in the case of swap, providing a swap operation with the no-throw guarantee is the simplest way of achieving the strong exception guarantee in your class, as you can copy aside, perform the operation on the copy, and then swap with the original.

But note that the language allows both destructors and swap to throw. swap can throw, in the simplest case if you do not overload it, then std::swap performs a copy construction, an assignment and a destruction, three operations that can each throw an exception (depending on your types).

The rules for destructors have changed in C++11, which means that a destructor without exception specification has an implicit noexcept specification which in turn means that if it threw an exception the runtime will call terminate, but you can change the exception specification to noexcept(false) and then the destructor can also throw.

At the end of the day, you cannot provide exception guarantees without understanding your code base, because pretty much every function in C++ is allowed to throw.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • 1
    +1 I thought I read on another post that swap must not throw (or otherwise it's undefined behavior), thanks for pointing out that doesn't have to be true. – user541686 Jul 23 '12 at 04:57
  • @Mehrdad: If you can provide a link to that post we can discuss the specifics. – David Rodríguez - dribeas Jul 23 '12 at 12:27
  • Yeah I'm definitely looking for it... it's just hard to find it again. :\ One of the posts that said "swap is non-throwing" is [this one](http://stackoverflow.com/a/3279550/541686), but it's less clear about that point than the one I saw before. – user541686 Jul 23 '12 at 12:38
  • 2
    +1. Didn't know this part *"The rules for destructors have changed in C++11, which means that a destructor without exception specification has an implicit `noexcept` specification which in turn means that if it threw an exception the runtime will call terminate, but you can change the exception specification to `noexcept(false)` and then the destructor can also throw"* – Nawaz Jul 23 '12 at 12:50
  • 1
    I would add that move constructors/assignment operators are much more useful if they are declared noexcept. – Dirk Holsopple Aug 15 '12 at 16:36
  • 2
    @curiousguy many situations, e.g. STL containers, use move_if_noexcept which will only use move the object if the move constructor is declared noexcept. – Dirk Holsopple Aug 20 '12 at 12:10
  • @DirkHolsopple "use move_if_noexcept which will only use move the object if the move constructor is declared noexcept." Thank you! – curiousguy Aug 20 '12 at 16:33
9

So this doesn't perfectly answer you question -- I searched for a bit out of my own curiosity -- but I believe that nothrow guaranteed functions/operators mostly originate from any C-style functions available in C++ as well as a few functions which are arbitrarily simple enough to give such a guarantee. In general it's not expected for C++ programs to provide this guarantee ( When should std::nothrow be used? ) and it's not even clear if such a guarantee buys you anything useful in code that makes regular use of exceptions. I could not find a comprehensive list of ALL C++ functions that are nothrow functions (please correct me if I missed a standard dictating this) other than listings of swap, destructors, and primitive manipulations. Also it seems fairly rare for a function that isn't fully defined in a library to require the user to implement a nothrows function.

So perhaps to get to the root of your question, you should mostly assume that anything can throw in C++ and take it as a simplification when you find something that absolutely cannot throw an exception. Writing exception safe code is much like writing bug free code -- it's harder than it sounds and honestly is oftentimes not worth the effort. Additionally there are many levels between exception unsafe code and strong nothrow functions. See this awesome answer about writing exception safe code as verification for these points: Do you (really) write exception safe code?. There's more information about exception safety at the boost site http://www.boost.org/community/exception_safety.html.

For code development, I've heard mixed opinions from Professors and coding experts on what should and shouldn't throw an exception and what guarantees such code should provide. But a fairly consistent assertion is that code which can easily throw an exception should be very clearly documented as such or indicate the thrown capability in the function definition (not always applicable to C++ alone). Functions that can possible throw an exception are much more common than functions that Never throw and knowing what exceptions can occur is very important. But guaranteeing that a function which divides one input by another will never throws a divide-by-0 exception can be quite unnecessary/unwanted. Thus nothrow can be reassuring, but not necessary or always useful for safe code execution.

In response to comments on the original question:

People will sometimes state that exception throwing constructors are evil when throw in containers or in general and that two-step initialization and is_valid checks should always be used. However, if a constructor fails it's oftentimes unfixable or in a uniquely bad state, otherwise the constructor would have resolved the problem in the first place. Checking if the object is valid is as difficult as putting a try catch block around initialization code for objects you know have a decent chance of throwing an exception. So which is correct? Usually whichever was used in the rest of the code base, or your personal preference. I prefer exception based code as it gives me a feeling of more flexibility without a ton of baggage code of checking every object for validity (others might disagree).

Where does this leave you original question and the extensions listed in the comments? Well, from the sources provided and my own experience worrying about nothrow functions in an "Exception Safety" perspective of C++ is oftentimes the wrong approach to handling code development. Instead keep in mind the functions you know might reasonably throw an exception and handle those cases appropriately. This is usually involving IO operations where you don't have full control over what would trigger the exception. If you get an exception that you never expected or didn't think possible, then you have a bug in your logic (or your assumptions about the function uses) and you'll need to fix the source code to adapt. Trying to make guarantees about code that is non-trivial (and sometimes even then) is like saying a sever will never crash -- it might be very stable, but you'll probably not be 100% sure.

Community
  • 1
  • 1
Pyrce
  • 8,296
  • 3
  • 31
  • 46
6

If you want the in-exhaustive-detail answer to this question go to http://exceptionsafecode.com/ and either watch the 85 min video that covers just C++03 or the three hour (in two parts) video that covers both C++03 and C++11.

When writing Exception-Safe code, we assume all functions throw, unless we know different.

In short,

*) Fundamental types (including arrays of and pointers to) can be assigned to and from and used with operations that don't involve user defined operators (math using only fundamental integers and floating point values for example). Note that division by zero (or any expression whose result is not mathematically defined) is undefined behavior and may or may not throw depending on the implementation.

*) Destructors: There is nothing conceptually wrong with destructors that emit exceptions, nor does the standard prohibited them. However, good coding guidelines usually prohibit them because the language doesn't support this scenario very well. (For example, if destructors of objects in STL containers throw, the behavior is undefined.)

*) Using swap() is an important technique for providing the strong exception guarantee, but only if swap() is non-throwing. In general, we can't assume that swap() is non-throwing, but the video covers how to create a non-throwing swap for your User-Defined Types in both C++03 and C++11.

*) C++11 introduces move semantics and move operations. In C++11, swap() is implemented using move semantics and the situation with move operations is similar to the situation with swap(). We cannot assume that move operations do not throw, but we can generally create non-throwing move operations for the User-Defined Types that we create (and they are provided for standard library types). If we provide non-throwing move operations in C++11, we get non-throwing swap() for free, but we may choose to implement our own swap() any way for performance purposes. Again, this is cover in detail in the video.

*) C++11 introduces the noexcept operator and function decorator. (The "throw ()" specification from Classic C++ is now deprecated.) It also provides for function introspection so that code can be written to handle situations differently depending on whether or not non-throwing operations exist.

In addition to the videos, the exceptionsafecode.com website has a bibliography of books and articles about exceptions which needs to be updated for C++11.

Jon Kalb
  • 103
  • 3