In C++11, a natural optimization would be to replace the copying of std::vectorelements with moves. Unfortunately, doing this runs the risk of violating push_back's exception safety guarantee. If n elements have been moved from the old memory and an exception is thrown moving element n+1 , the push_backoperation can't run to completion. But the original std::vectorhas been modified: n of its elements have been moved from. Restoring their original state may not be possible, because attempting to move each object back into the original memory may itself yield an exception.
This is a serious problem, because the behavior of legacy code could depend on push_back's strong exception safety guarantee. Therefore, C++11 implementations can't silently replace copy operations inside push_backwith moves unless it's known that the move operations won't emit exceptions. In that case, having moves replace copies would be safe, and the only side effect would be improved performance.
std::vector::push_backtakes advantage of this “move if you can, but copy if you must” strategy, and it's not the only function in the Standard Library that does. Other functions sporting the strong exception safety guarantee in C++98 (e.g., std::vector::reserve, std::deque::insert, etc.) behave the same way. All these functions replace calls to copy operations in C++98 with calls to move operations in C++11 only if the move operations are known to not emit exceptions. But how can a function know if a move operation won't produce an exception? The answer is obvious: it checks to see if the operation is declared noexcept. [3] The checking is typically rather roundabout. Functions like std::vector::push_back call std::move_if_noexcept , a variation of std::move that conditionally casts to an rvalue (see Item 23 ), depending on whether the type's move constructor is noexcept . In turn, std::move_if_noexcept consults std::is_nothrow_move_constructible , and the value of this type trait (see Item 9 ) is set by compilers, based on whether the move constructor has a noexcept (or throw() ) designation.
swap functions comprise another case where noexceptis particularly desirable. swapis a key component of many STL algorithm implementations, and it's commonly employed in copy assignment operators, too. Its widespread use renders the optimizations that noexceptaffords especially worthwhile. Interestingly, whether swaps in the Standard Library are noexceptis sometimes dependent on whether user-defined swaps are noexcept. For example, the declarations for the Standard Library's swaps for arrays and std::pairare:
template
void swap(T (&a)[N], // see
T (&b)[N]) noexcept(noexcept(swap(*a, *b))); // below
template
struct pair {
…
void swap(pair& p) noexcept(noexcept(swap(first, p.first)) &&
noexcept(swap(second, p.second)));
…
};
These functions are conditionally noexcept : whether they are noexceptdepends on whether the expressions inside the noexceptclauses are noexcept. Given two arrays of Widget, for example, swapping them is noexceptonly if swapping individual elements in the arrays is noexcept, i.e., if swapfor Widgetis noexcept. The author of Widget's swapthus determines whether swapping arrays of Widgetis noexcept. That, in turn, determines whether other swaps, such as the one for arrays of arrays of Widget, are noexcept. Similarly, whether swapping two std::pairobjects containing Widgets is noexceptdepends on whether swapfor Widgets is noexcept. The fact that swapping higher-level data structures can generally be noexceptonly if swapping their lower-level constituents is noexceptshould motivate you to offer noexcept swapfunctions whenever you can.
By now, I hope you're excited about the optimization opportunities that noexceptaffords. Alas, I must temper your enthusiasm. Optimization is important, but correctness is more important. I noted at the beginning of this Item that noexceptis part of a function's interface, so you should declare a function noexceptonly if you are willing to commit to a noexceptimplementation over the long term. If you declare a function noexceptand later regret that decision, your options are bleak. You can remove noexceptfrom the function's declaration (i.e., change its interface), thus running the risk of breaking client code. You can change the implementation such that an exception could escape, yet keep the original (now incorrect) exception specification. If you do that, your program will be terminated if an exception tries to leave the function. Or you can resign yourself to your existing implementation, abandoning whatever kindled your desire to change the implementation in the first place. None of these options is appealing.
The fact of the matter is that most functions are exception-neutral . Such functions throw no exceptions themselves, but functions they call might emit one. When that happens, the exception-neutral function allows the emitted exception to pass through on its way to a handler further up the call chain. Exception-neutral functions are never noexcept, because they may emit such “just passing through” exceptions. Most functions, therefore, quite properly lack the noexceptdesignation.
Some functions, however, have natural implementations that emit no exceptions, and for a few more — notably the move operations and swap— being noexceptcan have such a significant payoff, it's worth implementing them in a noexceptmanner if at all possible. [4] The interface specifications for move operations on containers in the Standard Library lack noexcept. However, implementers are permitted to strengthen exception specifications for Standard Library functions, and, in practice, it is common for at least some container move operations to be declared noexcept. That practice exemplifies this Item's advice. Having found that it's possible to write container move operations such that exceptions aren't thrown, implementers often declare the operations noexcept, even though the Standard does not require them to do so.
When you can honestly say that a function should never emit exceptions, you should definitely declare it noexcept.
Please note that I said some functions have natural noexceptimplementations. Twisting a function's implementation to permit a noexceptdeclaration is the tail wagging the dog. Is putting the cart before the horse. Is not seeing the forest for the trees. Is…choose your favorite metaphor. If a straightforward function implementation might yield exceptions (e.g., by invoking a function that might throw), the hoops you'll jump through to hide that from callers (e.g., catching all exceptions and replacing them with status codes or special return values) will not only complicate your function's implementation, it will typically complicate code at call sites, too. For example, callers may have to check for status codes or special return values. The runtime cost of those complications (e.g., extra branches, larger functions that put more pressure on instruction caches, etc.) could exceed any speedup you'd hope to achieve via noexcept, plus you'd be saddled with source code that's more difficult to comprehend and maintain. That'd be poor software engineering.
Читать дальше