noexcept
functions. My advice is "Declare functions noexcept
whenever possible." Some people appear to be concerned that this could be misconstrued as advocating noexcept
even when it makes no sense, but I think the advice is a reasonable conclusion to the Item that supports it. I posted a draft version of that Item in early February, but I've revised the draft since then, and I'm making the current draft available now:The current draft of "Declare functions
noexcept
whenever possible."I'd be interested to hear what you think of (1) the wording of the Item title and (2) the contents of the Item itself. I'd also be interested to know what you think about these questions:
- Should I say that
constexpr
functions are normally good candidates fornoexcept
? - Do lambdas get special consideration, or should they, too be declared
noexcept
whenever possible. If they're special, why? - Do
inline
functions get special consideration, or should they also be declarednoexcept
whenever possible? If they're special, how so?
Thanks,
Scott
Hi Scott,
ReplyDeleteOn page 2, line 12 to 15 to me it implies that there are some optimizations for callers that are only possible with noexcept.
I've been looking through the standard and it says that 'noexcept' and 'throw()' are 'compatible' (15.4/3) and so I would have expected the behavior for callers to be equivalent. What examples did you have in mind?
As I was reading this, it also surprised me to find that f1 and f2 will have different behavior:
void f1() throw();
void f1() noexcept { }
void f2() noexecpt;
void f2() throw() { }
Regards,
Richard
Hi Scott,
ReplyDeleteI think it'd be worthwhile to include some discussion about the interaction between noexcept and call contract violation. For instance, std::vector::operator[](size_type n) is *not* noexcept because n is required to be within range and an implementation *might* choose to throw when this is not the case, as a QoI feature. Not that I favor this approach (in fact I dislike it) but the whole thing deserves some talking about, don't you think?
Hi Scott,
ReplyDeleteI am not sure what to make of the advice, or better said of the wording of it: "Declare functions noexcept whenever possible" reads like there should be many many noexcept functions - as many as possible. On the other hand you state that "most functions are exception-neutral" and "Exception-neutral functions are never noexcept". However, it is possible to give those functions a noexcept declaration of some degree. With that and with the strong wording "whenever possible" I fear the dawn of noexcept specifiers listing any function used in the function's implementation:
void bar(int i) noexcept( noexcept(foo(i)) && noexcept(meow()) && noexcept(woof()) )
{
foo(i);
meow();
woof();
}
So perhaps the advice should be softened a bit, e.g. by restricting the set of functions ("Declare basic functions noexcept wherever possible"?) or by saying "... wherever reasonable"?
I'm not sure why you're of the opinion that constexpr functions are automatically good candidates for noexcept.
ReplyDeleteTake for example an imaginary function with a contract stating that it only accepts even numbers:
constexpr void only_evens(unsigned n)
{
if (n % 2)
{
throw std::invalid_argument("what an odd argument");
}
}
When called with an odd constant-expression, this will yield a compile error. At runtime, the breach of contract is indicated by the exception.
Like others here, I'm worried about the wording of this advice. I still work with some programmers who think exceptions are bad and argue against their use... Can it be something like: "Be aware of the extra optimizations available for noexcept functions."
ReplyDelete@Richard: From the perspective of this Item, the primary difference between noexcept and throw() is what I say on lines 2-6 of page 2: whether the call stack must be unwound in the event of a violated exception specification. (FWIW, whether it's unwound is not undefined behavior, it's implementation-defined.) There are some other behavioral differences (note "after some actions not relevant here" in lines 2-3 on page 2), notably whether the unexpected handler is invoked. In the case of throw(), it is. In the case of noexcept, it's not.
ReplyDelete@Joaquín M López Muñoz: To reiterate a comment I made on my last post, "My original outline for EMC++ had an Item on narrow and wide interfaces, but I was warned away from it on the advice of a committee member who said that the committee's enthusiasm for the idea was not widespread. How widespread that assessment of the situation is, however, I don't know."
ReplyDeleteThe idea is interesting, but the Items in the book are already too long (this one included), so I'm disinclined to add new information that's not really central to the topic.
@Arne Mertz: You make a legitimate point. On the other hand, if bar is used in a context where the optimizations opportunities afforded by noexcept are meaningful, maybe it should be declared that way, no?
ReplyDelete@Anonymous: My choice of the word "candidate" for constexpr functions was deliberate, because, as you point out, constexpr functions need not be evaluated during compilation and, in the general case, may emit exceptions. My expectation, however, is that most constexpr functions will likely have natural noexcept implementations, because they can take and return only literal types.
ReplyDelete@Tony Di Croce: The wording of this Item--or any of the Items--won't be finalized until the book goes to print. I wouldn't post this Item and ask for feedback were I not honestly interested in whether people think the wording is appropriate. My goal is to describe things in a way that will be helpful to practicing programmers and that can be technically defended. It's clear that the current wording of this Item raises some people's hackels. I'll definitely take that into account as I work on the book.
ReplyDelete@Scott, sorry for some extra nitpicking - following up on your reply to Richard. On page 2, line 13 you say "but also at sites where the function is called". Consider the following example:
ReplyDeletevoid f() throw();
void g() noexcept;
void foo() {
// ...
f();
// ...
}
void bar() {
// ...
g();
// ...
}
Is there any way a compiler could generate more optimised code for a call to f vs. g? Note that f could actually be defined with a "noexcept" exception specification (as that would be compatible with "throw()").
Chapter 3 is now many items longer than the other chapters. How about splitting it into two parts: 1) the C++11/14 syntactical novelties and 2) C++11/14 function interface design.
ReplyDeleteThe latter could then contain the items on override, const, constexpr, noexcept and parameter passing (a missing item would be the [[noreturn]] annotation).
@cmeerw: I appreciate your nitpicking. Nitpicking leads to truth. Well, sometimes it does, and I think this may be one of those cases. At the time I wrote those words, I was thinking of a function with and without noexcept, not a function with either noexcept or throw(). Now that I think about it more carefully, it's not apparent to me how callers of a noexcept function can be more optimized than callers of a throw() function. (It's a different story if it's a non-empty dynamic exception specification (e.g., "throw(std::exception)"), but that's not what I'm discussing in the Item.)
ReplyDeleteThanks for bringing this up.
@Anonymous: I agree, the distribution of the Items into chapters isn't well balanced. I'll make a note to change that.
ReplyDeleteThanks for the suggestion.
I find it interesting how the title points in one direction but later on you state:
ReplyDelete"As a general rule, the only time it makes sense to actively search for a noexcept algorithm is when you’re implementing the move functions or swap."
I completely agree with that, if not with the title.
In the general case, for functions with narrow contracts where you allow undefined behavior the use of throw specifications limit the possibility of what undefined means.
This is more apparent in the scope of n3604, where the user can configure the behavior of asserts at the application level.
@David Rodríguez: The sentence you quote is about actively searching for a noexcept implementation. That's different from declaring a function noexcept that has a natural noexcept implementation, which is what the Item title is more focused on.
ReplyDeleteAs for contract enforcement, if you, as implementer, wish to retain the right to throw exceptions for precondition violations, then of course you would not declare the function noexcept. I don't currently think that's worth calling out as a special consideration, but I acknowledge that other people feel differently. You're the third person to mention it so far :-)
Scott,
ReplyDeleteI'm curious to know if you have any sense of whether the compiler vendors are actually using 'noexcept' as an opportunity to optimize the code as you described? Have you spoken with Walter about what the digital mars compiler is doing? Or have you looked at some of the assembly code after gcc or msvc has compiled it?
Thanks, Jason
@Jason: My understanding is that at least Microsoft performs optimizations based on noexcept, because their (nonconforming) implementation of "throw()" in C++98 had the semantics of C++11's noexcept. It is further my understanding that Microsoft's experience with these semantics was one of the reasons the standardization committee decided to adopt them. For example, in the thread starting with this post, there are a couple of posts by Herb Sutter (e.g., this one) addressing the relationship between noexcept and optimzation. I assume that Herb's comments are based, at least in part, on Microsoft's experience.
ReplyDeleteI have not spoken with Walter, nor have I checked assembly output.
Hi Scott,
ReplyDeleteI would like to include some suggestions.
1. noexcept should not be mixed with "no-fail" guarantee. A function can offer a no-fail guarantee even if it is not noexcept. This is what swap() on STL containers does -- for a good reason (explained in N3248 (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3248.pdf)). This explains why many functions in STD library are not noexcept even though they appear a viable candidates. Strictly speaking, noexcept offers this: anyone can ask at compile time if a given function is declared as noexcept. The advice I try to follow is to put noexcept on move operations if I want move_if_noexcept to be able to pick my type.
2. There is no strong connection between noexcept and constexpr, and in fact recently I proposed for addition into a Library TS a function that is both throwing and constexpr, and it does make sense. It is optional<T>::value(): it throws when optional (like Boost.Optional) is not initialized, but someone might want to use it at compile-time, when they already know that an optional has been initialized. Also, constexpr functions that throw have nice effect of signalling errors at compile time. Here is an example: http://akrzemi1.wordpress.com/2011/05/06/compile-time-computations/
3. From inline functions (and I guess lambdas too), because they are short, I would expect exception neutrality more than noexcept. Who knows what the operations in their bodies throw or not?
4. Regarding "There are none [throwing destructors] in the Standard Library." -- this is true, but because of that there are destructors in the Standard that call std::terminate directly. I mean std::thread.
5. I am not sure about compiler optimizations (but this is just my feeling). Can't a global compiler optimization do the same, without our help of explicitly annotating functions as non-throwing?
FWIW, I also include a link to my article on noexcept: http://akrzemi1.wordpress.com/2011/06/10/using-noexcept/
Hope these help.
Regards,
&rzej
Your advice here is solid. However, I suggest a little less focus on optimization and more focus on correctness. For example, consider something like vector>. This would not be possible at all if unique_ptr's move constructor was not noexcept.
ReplyDelete@Anonymous: I assume you mean std::vector<std::unique_ptr<T>>, but I think you're mistaken about how this would not be possible without noexcept. Note that std::move_if_noexcept moves uncopyable types, even if their move operations may throw. I explain this in an older draft (but not the current one) of this Item. (Look on page 6, lines 19-23, of the older draft.)
ReplyDelete@akrzemi1: Regarding your comments:
ReplyDelete1. Several other people have already mentioned the interaction of noexcept, UB, and narrow contract enforcement. It increasingly seems like this is an issue I should address.
2. I understand that constexpr need not be noexcept, but I'd expect that constexpr functions would often be noexcept. My question was whether this is a reasonable expectation.
3. "Who knows what operations in [the body of an inline or lambda expression] throw or not?" The same people who know for non-inline and non-lambda functions: the functions' authors. I agree that most such functions--most functions in general--will be exception neutral, but the question is whether knowing that a function is inline-declared (as all lambdas are) should affect the decision to declare it noexcept.
5. Regarding compiler optimizations, I suspect that a compiler/linker with global knowledge could often infer noexcept, but not always. Such programs are almost always linked with some kind of separately-compiled libraries. As a practical matter, my understanding is that large programs can rarely use whole program optimization, because the in-memory images are so large, they either overflow the available address space (for 32 bit images) or they thrash during linking so badly, the build slows to a crawl.
Regarding 5, indeed global optimization like I imagine may be a fiction.
ReplyDeleteHowever, inline and constexpr functions do not get as far as to the linker in a typical case, so optimizing stack unwinding path for them should be doable without programmer's help.
I also find something rather psychologically wrong about me having to add so many annotations to every function. I used to type
X& getX() { return x; }
ok, adding const is acceptable:
X const& getX() const { retutn x; }
it sort of designates a subset of the class's interface. But that I should type the following for every small function is cruel:
noexcept constexpr x const& getX() const { return x; }
(Note that const has nothing to do with constexpr in C++14). These declarations are far longer than the function body. They clutter the code. And if I saw 12 declarations in a row, I would be tired checking, which is noexcept, but not constexpr, but const. and it is obvious the function is noexcept to me and to the compiler anyway.
Regards,
&rzej
My comment is slightly off topic, but one question stays in my mind.
ReplyDeleteWhy can't the compiler-linker combination detect automatically, if a function might throw. Putting that burden on the programmer feels somewhat outmoded in the 21st century.
@Anonymous: I imagine the big problems are separate compilation and dynamic linking. If all you have when you do code gen is a function prototype, there's no way to infer from the function body whether an exception might be emitted. (Managed languages that do code gen at runtime are not subject to these restrictions, I imagine.)
ReplyDelete@akrzemi1: I agree that the qualifiers can yield a lot of clutter. (noexcept goes at the end, BTW, not the beginning. But you could add static and volatile at the beginning. Also inline. And virtual. And at the end you could add final and override. And then there are attributes... :-})
ReplyDeleteI think I'm very against your advice. IMO the most important points one should remark are:
ReplyDelete- "make sure that your move constructor/move assignment/swap operators are noexcept(true)." This does not mean "make them noexcept(true)" but rather means static_assert that they are, and if they are not, try to find out why and try to fix that first before blatantly making them noexcept true. Recommending to blatantly make them noexcept is very wrong since they might be noexcept(false) for a good reason.
- "Avoid conditionally noexcept in generic code". Most of the time adds a lost of verbosity for no gain (I elaborate on this below). If you have generic one-liners, use Dave Abrahams/Eric Niebler's RETURNS( ) macro to correctly define your one liners noexcept.
- "Noexcept is part of a function interface. You can always add noexcept later, but you can't remove it without breaking client code". So think thrice about adding it!
- "Noexcept functions are not faster". If you have a benchmark in which due to making a function noexcept the compiler generates better code, please show it, I would love to see it! Noexcept allows algorithmic optimizations (e.g. use move instead of copy in operations with strong exception safety guarantee). But as the compiled code goes, exceptions cost nothing in the non-exceptional path, so saying "use noexcept, its faster!" is IMO extremely bad advice.
So in summary, IMO: "make move copy/assignment/swap noexcept, otherwise don't waste time thinking about it" is the best advice one can give. I guess that means I'm strongly against recommending "Declare functions noexcept whenever possible".
I want to add some remarks about using noexcept in generic code. I think you should mention that _right_ now, propagating noexcept correctly in generic code makes little sense. In particular, since most of the standard library is noexcept(false).
Example: std::plus is noexcept(false). The only thing that could throw inside std::plus is operator+. This might throw for some types, and might never throw (be noexcept(true)) for others. Still, since std::plus doesn't propagate this, it will always be noexcept(false), and so will be generic code trying to propagate noexcept correctly if that code uses std::plus.
Another example is lambdas, which are noexcept(false) by default. AFAIK no one writes noexcept specifications for lambdas, so if your operation takes a Functor/Predicate making it conditionally noexcept is not worth it since it will be always false for lambdas and std::function and will offer you no performance advantage.
So when it comes to the std, programmers should know that the committee marks functions noexcept depending on their contract, and thus that writing complex conditional noexcept specifications in generic code does not only introduce a lot of verbosity (better in capitals: A LOT) but is also almost always pointless. One could go as far as recommending "Prefer noexcept(false) to conditionally noexcept functions". Sadly I can't paraphrase Herb Sutter here (noexcept starts with an N), but noexcept is Almost Always pointless seems also good advice.
I overused noexcept a lot (as Bjarne correctly predicts for every new feature). IMO, it was not worth it. It introduces complexity (oh no, a noexcept interface function! That _can_ terminate my program! Do you have an alternative that throws instead of having a hidden std::terminate inside it?). It introduces a lot of verbosity (there is no noexcept(auto)). It offers no performance advantage (except for move construction/assignment/swap). It offers no extra type-safety (i.e. it is not transitive). And most of the std library is not noexcept transparent. So now I think that in most cases, thinking about it is a waste of time.
Bug fix: I said the only thing that can throw in std::plus is operator+. The copy/move construction of the return value can also throw.
ReplyDeleteBugfix2: " But as the compiled code goes, exceptions cost nothing in the non-exceptional path, so saying "use noexcept, its faster!" is IMO extremely bad advice."
ReplyDeleteWhat I also mean here is that in the exceptional path (where noexcept _could_ be faster), you don't care (and if you care you are doing it wrong).
Hi,
ReplyDeleteIt's a good thing to keep in mind that "if a function marked noexcept allows an uncaught exception to escape at runtime, std::terminate is called immediately". noexcept is about potential compiler optimization not compile time checking. A proliferation of noexcept declaration leads to a proliferation of std::terminate call. Just for that i wouldn't recommend to declare functions noexcept "whenever possible" but rather "whenever necessary and possible".
Hi Scott,
ReplyDeleteWell written as always. The only thing that struck me as being out of place was the title. I think that 'whenever possible' has the connotation of 'whenever the compiler will allow it', rather than the more wise 'when the contract guarantee makes sense'. The latter also seems to accord more with your discussion.
Alternate title ideas:
1) Declare functions noexcept whenever their contracts allow
2) Always consider if noexcept is appropriate for your functions
3) Enable compiler optimizations with judicious use of noexcept
@Mark: It seems clear that the current title is not conveying the information I want to get across, so I'm planning to change it. What do you you (and others) think of "Consider noexcept for performance-sensitive functions"?
ReplyDelete@Gonzalo BG: I believe most of your points are already present in my draft Item, e.g., strive for (but don't insist on) noexcept on move functions, note that noexcept is part of a function's interface.
ReplyDeleteRegarding the ability of compilers to generate better code in the presence of noexcept, I'm not a compiler writer, but consider this source code:
bool b;
...
try {
if (b) f1(); else f2();
}
catch (const std::exception& ex ) {
std::cout << "Ouch!" + ex.what();
}
If f1 and f2 (which we'll assume are separately compiled) are noexcept, no code need be generated for the catch clause. Either no exceptions will arise, in which case the catch clause is dead code, or f1 or f2 will violate its exception specification, in which case the program will terminate, the catch clause will never be reached, and the catch clause is again dead code.
But if f1 or f2 are not noexcept, the catch clause is not dead code, so compilers must emit instructions for it. This makes the overall program image larger. It could also increase the size of the containing function, and that, in turn, could put pressure on the instruction cache.
Furthermore, the I/O in the catch clause body is observable behavior, so I suspect that failure to eliminate the catch clause could restrict other compiler optimizations, e.g., code motion. But again, I'm not a compiler writer.
Am I mis-analyzing the situation? (Note that I'm not comparing noexcept to throw(), I'm comparing noexcept to no exception specification at all.)
I appreciate the added material and but I'm still unconvinced that "whenever possible" is really the best description of the item's content. The wording implies to me much more effort toward avoiding exceptions than the content recommends.
ReplyDeleteAlthough it occurs to me that there are two different times one could be considering whether to apply noexcept or not, and the phrase takes on different meanings at these two points.
One is prior to design/implementation where the decision to go with noexcept could dictate the design or implementation. This is where "whenever possible" seems to me to be the wrong wording.
The second point is after the design and implementation have been largely settled. Then "whenever possible" takes on quite a different meaning, being constrained to leave be a (presumably) reasonable implementation. If the intended advice is simply 'tack on noexcept at the end whenever possible' then I think the wording is perfectly reasonable.
Hi Scott,
ReplyDeleteI see your point, I hope you see my points too.
I checked your example and gcc 4.9 removes the dead code when the functions are noexcept, still this is IMO very low hanging fruit, and since noexcept is part of a function interface, why are you wrapping noexcept code in a try-catch in the first place?
Lets check Pros/Cons of noexcept. Starting with cons:
1) Once a function is made noexcept, you can't go back (API breakage).
2) To use it correctly you have to answer the question: "Am I sure that nothing in the context of this noexcept function call will throw?" This question is really hard! How should I know? Trying to answer it for every function you write is a huge mental burden!
3) Failing to answer 2 correctly inserts paths to std::terminate all over your code. Testing might not trigger these, but demoing the project to your client certainly will. Had you let exceptions propagate, some higher level code might already be in place to handle them.
4) It adds some verbosity and complexity to function declarations. In generic code, it adds a lot of verbosity and complexity to function declarations.
5) It can mask real problems: e.g. if instead of making move construction/assignment/swap noexcept you statically assert that they are, and they are not, there might be nothing wrong with your type, but you might be using a data member of some type with a throwing move assignment or what have you. Fixing that instead of making a function noexcept might allow algorithmic optimizations for a lot of types, not just your single type.
Pros:
1) Noexcept move constructor/move assignment/swap do enable algorithmic optimizations that consistently deliver much better performance.
2) There are some situations in which noexcept gives you some extra type safety (e.g. the exception specification of virtual functions is checked at compile-time for consistency). In this situations, noexcept is better than nothing.
3) If all the code within a try-block is noexcept, and you write the try-block anyways, then, the catch block will be removed by modern compilers which might allow some other optimizations.
So those are my 5 cons vs 3 pros (I know you have others, I hope these start a discussion). From the pros: 1) and 2) are worth it, I think one should strive for using noexcept in these cases. From the cons: 1), 2), and 5) are really bad, 3) is just a consequence of 2), but it happens if people use noexcept blindly due to promises of better performance. 4) is a bit subjective, but it is a real problem in generic code.
So do the pros out weight the cons?
Not in the general case, only for the situations explained in pros 1) and 2), which are a small subset of all the places in which one could use noexcept.
Why is this the case?
IMO because noexcept is just too hard to use correctly (see cons 2). In C++03 the right mindset for writing exception-safe code was "everything can and will throw". Stick to this, and writing exception-safe code is actually easy. However, fulfilling cons 2) is hard. There are some few cases in which the trouble is worth it: e.g. for critical operations (move constr./assignmt/swap) I let the compiler answer it for me (with static_assert) and make sure that it asserts to noexcept(true). But for the rest of my code? The cons out weight the pros by far.
Give me some help:
- transitivity (statically type-check that noexcept code only calls other noexcept code),
- an unsafe noexcept_cast to deal with context in which i know that a throwing function won't throw (and make those explicit), and
- noexcept(auto) to easily write generic code that propagates noexcept correctly,
and I will gladly reconsider.
@Seth: I had the second interpretation in mind, but, as I wrote in another comment, it's apparent that the current Item title doesn't convey what I'd like it to, so I'm planning to change it. What do you you (and others) think of "Consider noexcept for performance-sensitive functions"?
ReplyDeleteYeah, after the second interpretation occurred to me I suspected that it was closer to your intent all along. I'd been reading the title more like the first interpretation.
ReplyDeleteI'm wavering back and forth a bit on "Consider noexcept for performance-sensitive functions."
The mention of performance I think indicates that applying noexcept too early could be premature optimization which I think would cut down on the mistake I made in understanding the first title. At the same time I feel like _not_ applying noexcept to move/swap early would, in some cases, qualify as 'premature pessimization '. This second point is really the only issue I have with the title "Consider noexcept for performance-sensitive functions."
Perhaps it's that I have a good understanding of the performance gains related to enabling move semantics in containers and algorithms (and if I didn't before reading this chapter then I would afterwards), but I'm much more vague on the benefits of the compiler optimizations enabled by noexcept. I've seen some examples of better codegen but I don't have a good intuition for when they will kick in or how large the effect is. Different compilers seem to provide different levels of support (msvc seems to do more than gcc, which seems to do slightly more than clang), and I've even seen at least one comment by compiler devs that exception specifications could hurt codegen.
So I'm comfortable deciding up front if noexcept is appropriate on a move/swap function, and everywhere else I wouldn't want to do it until I could profile the results.
I suppose one of the reasons I liked "Know where to apply noexcept" (or "Know when to apply noexcept"?) was that that title doesn't try to squeeze in any indication of when/where that was; Any such shortened explanation seems doomed apparently contradict some part of the full explanation, with all its subtleties.
@Seth: In my view, applying noexcept to move operations without thinking about it is at least as big an error as failing to apply it when you can. noexcept move operations are desirable, but they're not required, and they may not always be possible. For example, std::array's move operation can't be unconditionally noexcept, and the move operations for the other STL containers are not required to be (and, last I checked, at least some are not in some implementations).
ReplyDeleteRegarding exception specs and code gen, it's certainly the case that if you have an exception spec, you have to generate code to check to make sure it's not violated. Exception specs have a cost. But my understanding is that in many cases the cost is more than compensated for by other optimization opportunities. Again, however, I am not a compiler writer.
It's worth noting, now that I think about it, that the implications of declaring a function noexcept extend beyond performance. It also gives callers a guarantee that they can use in determining what kind of exception-safety guarantee they will offer. Which kind of drives me back to a "whenever possible" perspective, because you'd like to give callers as many tools as you can for them to offer strong exception safety guarantees, no?
Scott
I didn't mean to say that applying noexcept to move/swap should _always_ be done, or done without thinking. Only that because the effects there are much better understood and apply across platforms I don't feel like I need to wait for profiling to know if noexcept is good there or not.
ReplyDeleteI haven't found any benchmarking of noexcept so I'm thinking about trying to do so myself. I think some real results would really help me understand the relative importance of those optimizations.
---
Jon Kalb's talk makes the same point in his presentation about how important noexcept is for users so that they can provide the exception guarantees they want, but still concludes that move/swap is the critical functionality.
@Seth: Seeing benchmark results would certainly be interesting, but given how recently noexcept was added to the language, it's not clear how many optimizations dependent on that feature we should expect to be implemented in current compilers.
ReplyDeleteThe current wording of "Declare functions noexcept whenever possible" feels to me like the type of thing that could morph into cargo cultism due to a perceived free lunch.
ReplyDeleteIt seems to be much more nuanced than the title suggests, as declaring a function noexcept is a very weighty decision to make. It can't be undone easily, and expresses some very hard-to-say-with-certainty things about a function and its callees for *eternity*. This type of scenario could lead to a very terrible situation: C++ code of the future riddled with poorly conceived "noexcept"'s everywhere due to a shallow impression that "Effective Modern C++ says noexcept makes code go faster!" The potential result: the keyword at becomes so misused that not only is it meaningless but compiler writers *can't* optimize for it.
It seems the clear and obvious big win that exists *now* for noexcept is the performance gains when declared properly on move/swap. This also is conceptually easy to understand and provide positive and negative examples for when noexcept is appropriate. Why not focus on this case alone for the Item?
The compiler optimization point seems to be a bit speculative, perhaps that can be included in a second edition if and when compiler writers manage to leverage it in a way that is well understood and has significant advantages. As stated in the item now, you can always add "noexcept" later, so it seems the book can always expand its advice in a way that further proliferates it's use properly.
It seems worth not opening pandora's box right now of "noexcept everywhere" if the use-cases that warrant the "whenever possible" choice of words seem minimal and perhaps even non-existent.
FWIW as per my advice above i'd propose a title along the lines of "Consider noexcept on move operations and swap"
ReplyDeleteAlso re: "It also gives callers a guarantee that they can use in determining what kind of exception-safety guarantee they will offer." I'm not sure if this is really that strong a point, since my understanding is that with unchecked exceptions, it is simply the "word" of the original author, akin to a "structured comment" or something, not a statically or dynamically enforced contract. (std::terminate hail mary aside)
ReplyDeleteFor example, if the author of the function you are calling just likely put it there because they heard you should use it "whenever you can" from somewhere it's an untrustworthy guarantee indeed :)
@Greg: I think it's important to bear in mind that, if you follow the examples in the language and standard library, more than move and swap functions benefit from noexcept. Destructors and operators delete are noexcept by default, and among the standard library functions that are unconditionally noexcept are std::move, std::forward, various functions in std::numeric_limits, abort, atexit/at_quick_exit/quick_exit, various functions in std::type_info... you can search the standard as easily as I can.
ReplyDeleteRegarding your last comment, noexcept is enforced at runtime. If you call a noexcept function, the caller will never see an exception from that function. Callers can thus use noexcept in their reasoning about the exception behavior of the functions they call. What you call the "std::terminate hail mary" is the basis of this guarantee.
Scott I think I may have emailed you a rant several years ago about exception guarantees. In it, I argued what you're arguing now: that a function with a non-throwing specification could be treated by the caller as offering the nothrow guarantee. Anyway, I wrote such a rant and emailed it to someone. I still agree with it and I agree with you now.
ReplyDeleteBut here's my question: How important is the nothrow guarantee?
The nothrow guarantee is critical for certain standard library facilities that are guaranteed not to crash (i.e. code without bugs). But even the STL only applies noexcept judiciously so as to allow debug out of range exceptions on containers and exceptions emitted from client code that's called by the library.
The default behavior of a propagated exception is that your program terminates. This is the *same* behavior offered by noexcept. But when you apply noexcept, you're forbidding yourself from modifying that default behavior to something potentially more useful.
The beauty of C++'s exception mechanism is that it allows most functions to completely ignore error handling. Exceptions make for cleaner, more efficient code. Personally, I like to put a function try block around my main function so that before I terminate, I catch and log any exceptions. Adding noexcepts to code is like adding try/catch blocks that call std::terminate(). If there happens to be a handler out there somewhere, noexcept prevents the exceptions from reaching it.
In the real world, it's very difficult to know whether a function will never emit and exception and never be *modified* to emit an exception. In the real world, there are good reasons (including debugging) why you might want to modify a function to make it throw. In the real world, noexcept makes code more brittle and harder to work with. It breaks the C++ exception mechanism. But even so, noexcept might be worth it sometimes.
So, when is noexcept worth it?
On page 6, you say, "As a general rule, the only time it makes sense to actively search for a 20 noexcept algorithm is when you’re implementing the move functions or swap."
A lot of the commenters here seem to agree with you.
But why are we writing our own move functions? Assuming we properly encapsulate our resources in smart pointers, when would we ever have to use anything but the compiler-generated move functions for our classes?
Also, why are we writing our own swap functions? What's wrong with std::swap? As long as our classes are copyable or moveable, std::swap should work fine.
Something else to keep in mind is that const can be checked at compile time while noexcept cannot. If you use either one enough, you'll eventually make a mistake. If you make a mistake with const, the compiler will catch you. But playing with noexcept is a dangerous game.
I agree with Seth that applying noexcept other than in very specific situations is premature optimization. I also agree with the bulk of what Gonzalo BG said. And I really liked akrzemi1's linked article. I'd be curious to know your thoughts on what he says in there.
Regarding the title, I like items that can easily go on a checklist. I'm not a fan of titles that begin with "Consider" or "Understand": "Did I consider this? I think so..." "Do I understand? I'm not sure..."
I wouldn't mind seeing the following items:
"Only write copy functions for classes with non-copyable members."
"Avoid writing your own swap functions."
"Avoid writing your own move functions."
"Keep your functions exception-neutral."
These are easy rules to follow: "Did I avoid writing swap functions? Yes." "Did I avoid writing move operations? Yes." "Did I keep my functions exception-neutral? Yes."
I wonder, is noexcept really part of a function interface?
ReplyDeleteI'm interested in opinions of more experienced people.
Assume for a second, that we have exactly three kinds of exception specifications:
noexcept(true) - function can't throw exceptions,
noexcept(false) - function can throw exceptions,
noexcept(auto) - it is unspecified (but known at compile time) if function can or can't throw exceptions.
(Further I'm talking only about functions for which noexcept(auto) is applicable - i.e. inline functions mostly).
Then explicit noexcept(true) and noexcept(false) are definitely parts of an interface, but noexcept(auto) is not. It still turns into noexcept(true) or noexcept(false) during compilation, but user can't actively rely on it.
And why is noexcept(auto) important? Because it denotes exception neutral functions. And, as was mentioned, most functions are exception neutral, even move operations and swap (just look at the standard library), even compiler-generated destructors.
(Another story, that you may static_assert noexceptness of your move/swap, just to be sure (Gonzalo BG).)
(The non-exception neutral functions are functions at module boundaries, and user-written destructors, and may be something else.)
I.e. for most of functions their noexcept specification is not part of their interface.
Currently absence of exception specification on inline functions can be viewed as noexcept(auto), which always turns into noexcept(false).
(Except for the standard library, which can freely add noexcept(true) to functions, not required to have exception specification by the standard).
So, I also wonder, if excepion specificatons of all inline non-noexcept functions (including lambdas!) are suddenly turned into noexcept(auto), would it be a breaking change?
@Vladimir, we're using the term "exception neutral" to describe functions that have no restrictions on the exceptions they're allowed to emit.
ReplyDeleteThe only functions that aren't exception neutral by default when you declare them are destructors and operator delete overloads. noexcept(false) explicitly denotes exception-neutral functions.
Could you clarify what you mean by noexcept(auto)?
Are you talking about this?
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3207.htm
I don't think there's any general way to determine whether a function might emit an exception without running into the halting problem. noexcept guarantees that a function won't throw by saying, "Hey, callers! If I try to throw an exception, I'll crash the program for you so you never have to deal with it."
@Alex Howlett
ReplyDelete>>we're using the term "exception neutral" to describe functions that have no restrictions on the exceptions they're allowed to emit.
I mean functions that just propagate all exceptions thrown inside of them and don't specifically deal with catching them (as in the Scott Meyers' draft and in the discussion above).
Exception neutral function can throw if it contains potentially throwing code and can't throw if it doesn't, i.e. its exception specification can be auto deduced.
>>Could you clarify what you mean by noexcept(auto)?
>>Are you talking about this?
>>http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3207.htm
Yes, exception specification deduced from function's body (very similar to auto return type).
>>I don't think there's any general way to determine whether a function might emit an exception without running into the halting problem.
It is already done (approximately, of course) for special member functions (N3937 15.4 Exception specifications [except.spec] 10). All of them have auto deduced exception specification by default.
@Vladimir, thanks for the clarification on "exception neutral." I wasn't drawing the distinction between functions that allow any exception to be emitted and functions that might have throw statements or try/catch blocks in their bodies.
ReplyDeleteIf noexcept(auto) denoted exception neutrality, the difference between noexcept(false) and noexcept(auto) would then be that noexcept(auto) functions would fail to compile if they tried to throw/catch their own exceptions, correct? Defaulting inline function to this form of noexcept(auto) would therefore be a breaking change for any inline function that contained a throw statement or a try/catch block.
But noexcept(auto) as described in n3207 does *not* denote exception neutrality. Any function deduced to be noexcept(false) would be exception neutral if and only if its body contained no throws or try/catch blocks.
The exception-neutrality of a function is not part of its interface. Both noexcept(false) and (user-specified) noexcept(true) functions can be exception neutral or not.
The noexcept status of a function is indeed part of its interface because the information is used by the callers.
Does that make sense?
Hi Scott,
ReplyDeletethis chapter contains very strong advice I would say. The only thing I would add is that there is another group of functions which almost always deserves noexcept:
Cleanup functions.
If we want exception safety, we need to put all the cleanup code in destructors (RAII). And as a result, those functions should really be noexcept.
What do you think about this?
Regards,
Arnaud
@Alex Howlett
ReplyDelete>>But noexcept(auto) as described in n3207 does *not* denote exception neutrality.
Sorry, "denote" is a wrong choice of a word.
noexcept(auto) is an appropriate/natural exception specification for an exception neutral function.
>>If noexcept(auto) denoted exception neutrality, the difference between noexcept(false) and noexcept(auto) would then be that noexcept(auto) functions would fail to compile if they tried to throw/catch their own exceptions, correct? Defaulting inline function to this form of noexcept(auto) would therefore be a breaking change for any inline function that contained a throw statement or a try/catch block.
No, no, no. My mistake with "denote" again. noexcept(auto) behaves as described in n3207.
>>Any function deduced to be noexcept(false) would be exception neutral if and only if its body contained no throws or try/catch blocks.
I do not understand. The deduction process itself (see n3207) is independent from presence of try/catch blocks and therefore exception neutrality.
>>The exception-neutrality of a function is not part of its interface. Both noexcept(false) and (user-specified) noexcept(true) functions can be exception neutral or not.
Correct.
>>The noexcept status of a function is indeed part of its interface because the information is used by the callers.
And that is the main question and my key point. Not everything used by the callers is a part of interface. sizeof(std::vector<int>) can be used too, but the concrete number is not specified and is not a part of vector's interface. And I see the similar situation with exception specifications. I'll describe it again by summarizing my previous words:
1) noexcept(auto) (or its imitation) is a natural exception specification for exception neutral functions (i.e. most of functions)
2) concrete exception specification deduced by noexcept(auto) is an implementation detail and not part of an interface. It is unspecified and can be changed (even between Debug/Release modes, see Library Issue 2319 about debug STL in Visual C++ as a related concrete example).
3) although unspecified, it can be productively used, e.g. by std::move_if_noexcept and similar switching functions
4) conclusion: for most of functions their natural exception specification is not a part of their interface
Does that make sense? :)
@Arnaud: I'd hope that the material on page 8, lines 21-25, and page 9, lines 1-7, would make clear that destructors and operators delete should normally be noexcept. No?
ReplyDeleteScott
@Vladimir: Whether a function is noexcept is certainly part of its interface, because client code may make decisions that depend on whether a function is noexcept. The most obvious example is the use of std::move_if_noexcept, but the fundamental question of whether a function is guaranteed to return to its caller (short of extralinguistic events such as somebody killing a process) is affected by whether functions it calls are noexcept.
ReplyDeleteNote that if we had noexcept(auto), callers could still write code that depends on whether a function is noexcept(true), because they could use noexcept expressions.
As I noted in another comment, there are a number of noexcept functions in the standard library that have nothing to do with moving or swapping, and I don't think they can be characterized as being at module boundaries. Examples include std::move and std::forward.
I am unaware of any code that would be broken if a function that was noexcept(false) became noexcept(true), so introducing noexcept(auto) as a default would not, as far as I can tell (with only minimal thinking about it), be a breaking change. This is true for both inline and non-inline functions, I think.
@Alex: I looked up our email exchange regarding exceptions. It's from 2007. You have a very good memory :-)
ReplyDeleteRegarding std::swap versus a class-specific swap, the general reason for writing your own is that it can be more efficient. Consider the cost of moving a std::unique_ptr. It can be thought of as two raw pointer assignments: one to copy the source to the destination, and one to set the source to null. (This is a simplification, because if we're doing a move assignment, and the destination isn't null, its pointee has to be destroyed, but we'll ignore that.) If I know I want to swap two std::unique_ptrs, I expect to pay three raw pointer assignments: dest to temp, source to dest, temp to source. If I put a std::unique_ptr into a class and let the class generate the move operations, the cost of moving a class object is the same as moving the std::unique_ptr it contains: two raw pointer assignments. But if I use std::swap on a class object, I perform three move operations on that object (dest to temp, source to dest, temp to source), and, at two raw pointer assignments per object move operation, that totals six raw pointer assignments. On its face, that's twice as expensive, though compilers may be able to optimize some or all of the extra cost away via dead assignment analysis. This is why the standard containers write their own swap routines. Behaviorally, std::swap is fine. In terms of performance, it may not be optimal.
Regarding your preference for guidelines that can be put on a checklist, I fully agree with you (see Item 3 here), and I thank you for reminding me of this. I'll work harder to eliminate guidelines that begin with "Consider" and "Understand".
@Vladimir: Interfaces don't have to be fully specified by the standard. sizeof(std::vector<int>) is an unspecified part of std::vector<int>'s interface. Similarly, noexcept can go unspecified without making it any less part of a function's interface.
ReplyDeleteThat being said, I agree with you that some level of noexcept deduction might be desirable. Stroustrup makes the case for it here: www.stroustrup.com/N3202-noexcept.pdf
@Scott: Wow. That's awesome that you still have that correspondence. Do you think you could email it back to me? Among other things, I think it included some weird half-cooked notion of concepts that I'd like to read again.
ReplyDeleteThat's a really good point about swapping std::unique_ptrs. So while std::swap isn't nearly as expensive as it was before move semantics, it's still got some overhead.
It's too bad that swap doesn't default to a member-wise swap the way copy/move operations default to member-wise copy/move. It would eliminate the need for many STL classes to have custom swaps.
I hadn't read the Effective Effective Books post before. It's great advice. I like Item 5. I particularly like how three of my suggested Item titles violate it. =P
-Only write copy functions for classes with non-copyable members.
+Use compiler-generated copy functions for classes with copyable members.
-Avoid writing your own swap functions
+Prefer std::swap over rolling your own swap function.
(Not sure if this is still good advice given your example)
-Avoid writing your own move functions
+Use compiler-generated move functions to implement move semantics.
I also might revise the fourth one based on Item 9:
-Keep your functions exception neutral.
+Prefer your functions to be exception neutral.
This could simultaneously address the potential overuse of noexcept as well as the overuse of try/catch blocks that you see in the "Java++" style of coding. We all throw sometimes and even catch every once in a while, but these cases should be the exception (see what I did there?) rather than the rule. Similarly, I feel that making swap functions noexcept should be an exception to the rule.
Item 13 has inspired me to take another look at your table of contents.
@Alex Howlett: I just sent copies of our old correspondence to the email address you were using in January 2007. If you don't get my message, send me your current email address (I'm at smeyers@aristeia.com), and I'll send the messages again.
ReplyDeleteHi Scott,
ReplyDeletemy point was that good candidates for noexcept are functions which wants to be called from destructors (like unlock on mutexes for example).
The paragraph focuses on destructors themselves not on what can be safely called from them. IMHO cleanup(), unlock(), release(), close() and the likes should often be noexcept too.
Hope this is clearer.
Regards,
Arnaud
@Arnaud: Ah, I see what you mean. Good point. I agree with you in principle, but I think it might be tricky to determine, in general, which functions are likely to be called inside destructors. Nevertheless, your point stands.
ReplyDelete