For some functions, being noexcept
is so important, they're that way by default. In C++98, it was considered bad style to permit the memory deallocation functions (i.e., operator delete
and operator delete[]
) and destructors to emit exceptions, and in C++11, this style rule has been all but upgraded to a language rule. By default, all memory deallocation functions and all destructors — both user-defined and compiler-generated — are implicitly noexcept
. There's thus no need to declare them noexcept
. (Doing so doesn't hurt anything, it's just unconventional.) The only time a destructor is not implicitly noexcept
is when a data member of the class (including inherited members and those contained inside other data members) is of a type that expressly states that its destructor may emit exceptions (e.g., declares it “ noexcept(false)
”). Such destructors are uncommon. There are none in the Standard Library, and if the destructor for an object being used by the Standard Library (e.g., because it's in a container or was passed to an algorithm) emits an exception, the behavior of the program is undefined.
It's worth noting that some library interface designers distinguish functions with wide contracts from those with narrow contracts . A function with a wide contract has no preconditions. Such a function may be called regardless of the state of the program, and it imposes no constraints on the arguments that callers pass it. [5] “Regardless of the state of the program” and “no constraints” doesn't legitimize programs whose behavior is already undefined. For example, std::vector::size has a wide contract, but that doesn't require that it behave reasonably if you invoke it on a random chunk of memory that you've cast to a std::vector . The result of the cast is undefined, so there are no behavioral guarantees for the program containing the cast.
Functions with wide contracts never exhibit undefined behavior.
Functions without wide contracts have narrow contracts. For such functions, if a precondition is violated, results are undefined.
If you're writing a function with a wide contract and you know it won't emit exceptions, following the advice of this Item and declaring it noexcept
is easy. For functions with narrow contracts, the situation is trickier. For example, suppose you're writing a function f taking a std::string
parameter, and suppose f
's natural implementation never yields an exception. That suggests that f should be declared noexcept
.
Now suppose that f
has a precondition: the length of its std::string
parameter doesn't exceed 32 characters. If f were to be called with a std::string
whose length is greater than 32, behavior would be undefined, because a precondition violation by definition results in undefined behavior. f is under no obligation to check this precondition, because functions may assume that their preconditions are satisfied. (Callers are responsible for ensuring that such assumptions are valid.) Even with a precondition, then, declaring f noexcept
seems appropriate:
void f(const std::string& s) noexcept; // precondition:
// s.length() <= 32
But suppose that f
's implementer chooses to check for precondition violations. Checking isn't required, but it's also not forbidden, and checking the precondition could be useful, e.g., during system testing. Debugging an exception that's been thrown is generally easier than trying to track down the cause of undefined behavior. But how should a precondition violation be reported such that a test harness or a client error handler could detect it? A straightforward approach would be to throw a “precondition was violated” exception, but if f is declared noexcept
, that would be impossible; throwing an exception would lead to program termination. For this reason, library designers who distinguish wide from narrow contracts generally reserve noexcept
for functions with wide contracts.
As a final point, let me elaborate on my earlier observation that compilers typically offer no help in identifying inconsistencies between function implementations and their exception specifications. Consider this code, which is perfectly legal:
void setup(); // functions defined elsewhere
void cleanup();
void doWork() noexcept{
setup(); // set up work to be done
… // do the actual work
cleanup(); // perform cleanup actions
}
Here, doWork
is declared noexcept
, even though it calls the non- noexcept
functions setup
and cleanup
. This seems contradictory, but it could be that setup
and cleanup
document that they never emit exceptions, even though they're not declared that way. There could be good reasons for their non- noexcept
declarations. For example, they might be part of a library written in C. (Even functions from the C Standard Library that have been moved into the std
namespace lack exception specifications, e.g., std::strlen
isn't declared noexcept
.) Or they could be part of a C++98 library that decided not to use C++98 exception specifications and hasn't yet been revised for C++11.
Because there are legitimate reasons for noexcept
functions to rely on code lacking the noexcept
guarantee, C++ permits such code, and compilers generally don't issue warnings about it.
Things to Remember
• noexcept
is part of a function's interface, and that means that callers may depend on it.
• noexcept
functions are more optimizable than non -noexcept
functions.
• noexcept
is particularly valuable for the move operations, swap
, memory deallocation functions, and destructors.
• Most functions are exception-neutral rather than noexcept
.
Item 15: Use constexpr
whenever possible.
If there were an award for the most confusing new word in C++11, constexpr
would probably win it. When applied to objects, it's essentially a beefed-up form of const
, but when applied to functions, it has a quite different meaning. Cutting through the confusion is worth the trouble, because when constexpr
corresponds to what you want to express, you definitely want to use it.
Conceptually, constexpr
indicates a value that's not only constant, it's known during compilation. The concept is only part of the story, though, because when constexpr
is applied to functions, things are more nuanced than this suggests. Lest I ruin the surprise ending, for now I'll just say that you can't assume that the results of constexpr
functions are const
, nor can you take for granted that their values are known during compilation. Perhaps most intriguingly, these things are features . It's good that constexpr
functions need not produce results that are const
or known during compilation!
But let's begin with constexpr
objects. Such objects are, in fact, const
, and they do, in fact, have values that are known at compile time. (Technically, their values are determined during translation , and translation consists not just of compilation but also of linking. Unless you write compilers or linkers for C++, however, this has no effect on you, so you can blithely program as if the values of constexpr
objects were determined during compilation.)
Читать дальше