When should I really use noexcept?
The noexcept
keyword can be appropriately applied to many function signatures, but I am unsure as to when I should consider using it in practice. Based on what I have read so far, the last-minute addition of noexcept
seems to address some important issues that arise when move constructors throw. However, I am still unable to provide satisfactory answers to some practical questions that led me to read more about noexcept
in the first place.
There are many examples of functions that I know will never throw, but for which the compiler cannot determine so on its own. Should I append noexcept
to the function declaration in all such cases?
Having to think about whether or not I need to append noexcept
after every function declaration would greatly reduce programmer productivity (and frankly, would be a pain in the ass). For which situations should I be more careful about the use of noexcept
, and for which situations can I get away with the implied noexcept(false)
?
When can I realistically expect to observe a performance improvement after using noexcept
? In particular, give an example of code for which a C++ compiler is able to generate better machine code after the addition of noexcept
.
Personally, I care about noexcept
because of the increased freedom provided to the compiler to safely apply certain kinds of optimizations. Do modern compilers take advantage of noexcept
in this way? If not, can I expect some of them to do so in the near future?
I think it is too early to give a "best practices" answer for this as there hasn't been enough time to use it in practice. If this was asked about throw specifiers right after they came out then the answers would be very different to now.
Having to think about whether or not I need to append noexcept
after every function declaration would greatly reduce programmer productivity (and frankly, would be a pain).
Well then use it when it's obvious that the function will never throw.
When can I realistically expect to observe a performance improvement after using noexcept
? [...] Personally, I care about noexcept
because of the increased freedom provided to the compiler to safely apply certain kinds of optimizations.
It seems like the biggest optimization gains are from user optimizations, not compiler ones due to the possibility of checking noexcept
and overloading on it. Most compilers follow a no-penalty-if-you-don't-throw exception handling method so I doubt it would change much (or anything) on the machine code level of your code, although perhaps reduce the binary size by removing the handling code.
Using noexcept
in the big 4 (constructors, assignment, not destructors as they're already noexcept
) will likely cause the best improvements as noexcept
checks are 'common' in template code such as in std containers. For instance, std::vector
won't use your class's move unless it's marked noexcept
(or the compiler can deduce it otherwise).
As I keep repeating these days: semantics first .
Adding noexcept
, noexcept(true)
and noexcept(false)
is first and foremost about semantics. It only incidentally condition a number of possible optimizations.
As a programmer reading code, the presence of noexcept
is akin to that of const
: it helps me better grok what may or may not happen. Therefore, it is worthwhile spending some time thinking about whether or not you know if the function will throw. For reminder, any kind of dynamic memory allocation may throw.
Okay, now on to the possible optimizations.
The most obvious optimizations are actually performed in the libraries. C++11 provides a number of traits that allows knowing whether a function is noexcept
or not, and the Standard Library implementation themselves will use those traits to favor noexcept
operations on the user-defined objects they manipulate, if possible. Such as move semantics.
The compiler may only shave a bit of fat (perhaps) from the exception handling data, because it has to take into account the fact that you may have lied. If a function marked noexcept
does throw, then std::terminate
is called.
These semantics were chosen for two reasons:
noexcept
even when dependencies do not use it already (backward compatibility) noexcept
when calling functions that may theoretically throw but are not expected to for the given arguments This actually does make a (potentially) huge difference to the optimizer in the compiler. Compilers have actually had this feature for years via the empty throw() statement after a function definition, as well as propriety extensions. I can assure you that modern compilers do take advantage of this knowledge to generate better code.
Almost every optimization in the compiler uses something called a "flow graph" of a function to reason about what is legal. A flow graph consists of what are generally called "blocks" of the function (areas of code that have a single entrance and a single exit) and edges between the blocks to indicate where flow can jump to. Noexcept alters the flow graph.
You asked for a specific example. Consider this code:
void foo(int x) {
try {
bar();
x = 5;
// other stuff which doesn't modify x, but might throw
} catch(...) {
// don't modify x
}
baz(x); // or other statement using x
}
The flow graph for this function is different if bar
is labeled noexcept
(there is no way for execution to jump between the end of bar
and the catch statement). When labeled as noexcept
, the compiler is certain the value of x is 5 during the baz function - the x=5 block is said to "dominate" the baz(x) block without the edge from bar()
to the catch statement. It can then do something called "constant propagation" to generate more efficient code. Here if baz is inlined, the statements using x might also contain constants and then what used to be a runtime evaluation can be turned into a compile-time evaluation, etc.
Anyway, short answer: noexcept
lets the compiler generate a tighter flow graph, and the flow graph is used to reason about all sorts of common compiler optimizations. To a compiler, user annotations of this nature are awesome. The compiler will try to figure this stuff out, but it usually can't (the function in question might be in another object file not visible to the compiler or transitively use some function which is not visible), or when it does there is some trivial exception which might be thrown that you're not even aware of so it can't implicitly label it as noexcept
(allocating memory might throw bad_alloc, for example).
上一篇: 使用双逻辑非(!!)运算符困惑
下一篇: 我应该什么时候使用noexcept?