Assertions <cassert>
Assertions
are Boolean expressions that are expected to be true at a given point in the code. The assert macro of <cassert> is defined similar to this:
#ifdef NDEBUG
#define assert(_)
#else
#define assert(CONDITION) if (!CONDITION) { print_msg(...); std::abort(); }
#endif
If an assertion fails, a
diagnostic message is written to the standard error output, and std::abort() is called, which terminates the application without performing any cleanup. While debugging an application, certain IDEs give you the option to continue the execution if an assertion fails. Common practice is to use assertions as a debugging aid and to define NDEBUG when building a release build of your application, turning asserts into no-operations.
Assertions are generally used to check invariants, such as loop invariants, or function pre- and postconditions. One example is parameter validation:
A possible output of this program is
Assertion failed: msg != nullptr, file d:TestTest.cpp, line 13
Caution
Make sure the condition you provide to assert() does not have any side effects that are required for the proper execution of your program, because this expression is not evaluated if NDEBUG is defined (for example, for a release build).
Exceptions <exception>, <stdexcept>
std::exception, defined in <exception>, is not intended to be thrown itself but instead serves as a base class for all exceptions defined by the Standard Library and can serve as a base class for your own. Figure 8-1 outlines all standard exceptions.
Figure 8-1.
The C++ Standard Library exception hierarchy
An exception can be copied and offers a
what() method
that returns a string representation of the error. This function is virtual and should be overridden. The return type is const char*, but the character encoding is not specified (Unicode strings encoded as UTF-8 could be used, for instance; see Chapter 6).
The exceptions defined in
<stdexcept>
are the only standard exceptions that are intended to be thrown by application code. As a rule, logic_errors represent avoidable errors in the program’s logic, whereas runtime_errors are caused by less predictable events beyond the scope of the program. logic_error, runtime_error, and most of their subclasses (except system_errors and future_error, which require an error code, as discussed later) must be passed a std::string or const char* pointer upon construction, which is returned by what() afterward. There is thus no need to further override what().
Exception Pointers <exception>
The <exception> header provides std::exception_ptr, an unspecified pointer-like type, used to store and transfer caught exceptions even without knowing the concrete exception type. An exception_ptr can point to a value of any type, not just an std::exception. It can point to a custom exception class, an integer, a string, and so on. Any pointed-to value stays valid while there is at least one exception_ptr still referring to it (that is, a reference-counted smart pointer may be used to implement exception_ptr).
A couple of functions are defined in <exception> to work with exception pointers:
exception_ptr std::current_exception() noexcept
- Creates and returns an exception_ptr that refers to the exception (remember, this can have any type) currently in flight when called from inside a catch() block, either directly or indirectly (a catch() block may call, for example, a helper function to handle an exception). The returned exception_ptr refers to a null value if called when no exception is being handled.
template<typename T>
exception_ptr std::make_exception_ptr(T t) noexcept
- Creates and returns an exception_ptr that points to t.
[[noreturn]] void std::rethrow_exception(exception_ptr)
- Rethrows the exception to which the given exception_ptr points. This is the only way to obtain the object pointed to by an exception_ptr. An exception_ptr cannot be dereferenced, nor is there a getter function.
Once created, exception_ptrs can be copied, compared, and in particular assigned and compared with nullptr. This makes them useful to store and move exceptions around and to test later whether an exception has occurred. For this, an exception_ptr is also convertible to a Boolean: true if it points to an exception, false if it is a null pointer. Default constructed instances are equivalent to nullptr.
Exception pointers can be used, for example, to transfer exceptions from a worker thread to the main thread (note that this is essentially what the utilities of <future> discussed in the previous chapter implicitly do for you, as well):
Nested Exceptions <exception>
The <exception> header also offers facilities to work with
nested
exceptions. They allow you to wrap a caught exception in another one: for instance, to augment it with extra context information or to convert it to a more suitable exception for your application. std::nested_exception is a copyable mixin1 class whose default constructor captures current_exception() and stores it. This nested exception can be retrieved as an exception_ptr with nested_ptr(), or by using rethrow_nested(), which rethrows it. Take care, though: std::terminate() is called when rethrow_nested() is called without any stored exception. It is therefore generally recommended that you not use nested_exception directly, but use these helper methods instead:
[[noreturn]] template<typename T> void std::throw_with_nested(T&& t)
- Throws an undefined type deriving from both std::nested_exception and T (with reference qualifiers stripped), which can be handled using a regular catch (const T&) expression, ignoring the nested exception. Being a std::nested_exception as well, it also contains the result of std::current_exception(), which may optionally be retrieved and handled.
template <typename T> void std::rethrow_if_nested(const T& t)
- If t derives from nested_exception, calls rethrow_nested() on it; otherwise does nothing.
The following example demonstrates nested exceptions:
void execute_helper() {
throw std::range_error("Out-of-range error in execute_helper()");
}
void execute() {
try { execute_helper(); }
catch (...) {
std::throw_with_nested(std::runtime_error("Caught in execute()"));
}
}
void print(const std::exception& exc) {
std::cout << "Exception: " << exc.what() << std::endl;
try { std::rethrow_if_nested(exc); }
catch (const std::exception& e) {
std::cout << " Nested ";
print(e);
}
}
int main() {
try { execute(); }
catch (const std::exception& e) { print(e); }
}
The output of this piece of code is as follows:
Exception: Caught in execute()
Nested Exception: Out-of-range error in execute_helper()
System Errors <system_error>
Errors from the
operating system or other low-level APIs are called system errors. These are handled by classes and functions
defined in the <system_error> header:
- error_code: Generally wraps a platform-specific error code (an int), although for some categories the error codes are defined by the standard (see Table 8-1).Table 8-1.Available Error Category Functions and Corresponding Error Condition and Error Code Enum ClassesSingleton FunctionError ConditionsError CodesHeadergeneric_category()std::errc<system_error>system_category()<system_error>iostream_category()std::io_errc<ios>future_category()std::future_errc<future>
- error_condition: Wraps a portable, platform-independent error condition (an int). The enum class std::errc lists the built-in conditions. They correspond to the standard POSIX error codes, defined also as macros in <cerrno>. See Table 8-2 at the end of this chapter.Table 8-2.std::errc Error Condition Values and Corresponding <cerrno> Macrosstd::errc enum Value<cerrno> Macroaddress_family_not_supportedEAFNOSUPPORTaddress_in_useEADDRINUSEaddress_not_availableEADDRNOTAVAILalready_connectedEISCONNargument_list_too_longE2BIGargument_out_of_domainEDOMbad_addressEFAULTbad_file_descriptorEBADFbad_messageEBADMSGbroken_pipeEPIPEconnection_abortedECONNABORTEDconnection_already_in_progressEALREADYconnection_refusedECONNREFUSEDconnection_resetECONNRESETcross_device_linkEXDEVdestination_address_requiredEDESTADDRREQdevice_or_resource_busyEBUSYdirectory_not_emptyENOTEMPTYexecutable_format_errorENOEXECfile_existsEEXISTfile_too_largeEFBIGfilename_too_longENAMETOOLONGfunction_not_supportedENOSYShost_unreachableEHOSTUNREACHidentifier_removedEIDRMillegal_byte_sequenceEILSEQinappropriate_io_control_operationENOTTYinterruptedEINTRinvalid_argumentEINVALinvalid_seekESPIPEio_errorEIOis_a_directoryEISDIRmessage_sizeEMSGSIZEnetwork_downENETDOWNnetwork_resetENETRESETnetwork_unreachableENETUNREACHno_buffer_spaceENOBUFSno_child_processECHILDno_linkENOLINKno_lock_availableENOLOCKno_messageENOMSGno_message_availableENODATAno_protocol_optionENOPROTOOPTno_space_on_deviceENOSPCno_stream_resourcesENOSRno_such_deviceENODEVno_such_device_or_addressENXIOno_such_file_or_directoryENOENTno_such_processESRCHnot_a_directoryENOTDIRnot_a_socketENOTSOCKnot_a_streamENOSTRnot_connectedENOTCONNnot_enough_memoryENOMEMnot_supportedENOTSUPoperation_canceledECANCELEDoperation_in_progressEINPROGRESSoperation_not_permittedEPERMoperation_not_supportedEOPNOTSUPPoperation_would_blockEWOULDBLOCKowner_deadEOWNERDEADpermission_deniedEACCESprotocol_errorEPROTOprotocol_not_supportedEPROTONOSUPPORTread_only_file_systemEROFSresource_deadlock_would_occurEDEADLKresource_unavailable_try_againEAGAINresult_out_of_rangeERANGEstate_not_recoverableENOTRECOVERABLEstream_timeoutETIMEtext_file_busyETXTBSYtimed_outETIMEDOUTtoo_many_files_openEMFILEtoo_many_files_open_in_systemENFILEtoo_many_linksEMLINKtoo_many_symbolic_link_levelsELOOPvalue_too_largeEOVERFLOWwrong_protocol_typeEPROTOTYPE
- error_category: Error codes and conditions belong to a category. The category singleton objects are responsible for converting between both numberings.
- system_error: An exception class (see Figure 8-1) with an extra code() member returning an error_code.
In addition to a numeric value, both error_code and error_condition objects hold a reference to their error_category. Within one category, a number is unique, but the same number may be used by different categories.
All this may seem fairly complicated, but the main uses of these errors remain straightforward. To compare a given error code, such as that from a caught system_error exception, with either an error condition or a code, the == and != operators can be used. For instance:
if (systemError.code() == std::errc::argument_out_of_domain)
...
Note
std::error_category
The different std::error_category instances are implemented as singletons: that is, there is only one global, non-copyable instance per category. A number of predefined categories exist, obtainable from the global functions listed in Table 8-1.
An std::error_category has the following methods:
Member | Description |
---|---|
name()
| Returns the category’s name (as a const char*). |
message()
| Returns an explanatory std::string for a given error condition value (an int). |
default_error_condition()
| Converts a given error code value (an int) to a portable error_condition. |
equivalent()
| Compares error codes with portable conditions. It is easier to use the == and != operators shown earlier, instead.
|
std::error_code
std::error_code encapsulates an error-code value and an error_category. There are three constructors:
- A default one that sets the error code to 0 (this conventionally represents “no error”) and associates it with system_category.
- One accepting an error code int and an error_category.
- One constructing an error_code from an error-code enumeration value e by calling std::make_error_code(e). The parameter type must be an error-code enumeration type, an enumeration type for which the std::is_error_code_enum type trait has a value of true (see Chapter 2 for type traits). This automatically sets the correct category as well. The enum classes for the standard categories are shown in Table 8-1.
To raise your own std::system_error, you have to provide an error_code, which can be created with one of its constructors or with make_error_code(). For example:
std::error_code provides the following methods:
Method | Description |
---|---|
assign(int,
error_category&)
| Assigns the given error code and category to this error_code
|
operator=
| Uses std::make_error_code() to assign a given error-code enumeration value to this error_code
|
clear()
| Sets the error code to 0 and the category to system_category to represent no error |
int value()
error_category& category()
| Returns the error value / associated category |
error_condition
default_error_condition()
| Calls category().default_error_condition(value()), returning the corresponding portable error condition |
string message()
| Calls category().message(value())
|
operator bool
| Returns true if the error code is not 0
|
std::error_condition
The std::error_condition class encapsulates a portable condition code and the associated error category. This class has a set of constructors and methods similar to error_code, except
- It does not have a default_error_condition() method or equivalent function to go from error condition to error code.
- Error condition enumerations are used instead of error code enumerations: those enum types for which the is_error_condition_enum type trait has a value of true.
- Members that use std::make_error_code() use std::make_error_condition() instead.
C Error Numbers <cerrno>
The <cerrno> header defines errno, a macro that expands to a value equivalent to int&. Functions can set the value of errno to a specific error value to signal an error. A separate errno is provided per thread of execution. Setting errno is very common for functions from the C headers. The C++ libraries mostly throw exceptions upon failure, although some set errno as well (std::string-to-numeric conversions, for example). Table 8-2 lists the macros with default POSIX error numbers defined by <cerrno>.
If you want to use errno to detect errors in functions that use errno to report errors, then you have to make sure to set errno to 0 before calling the function, as is done in this example (needs <cmath>)2:
The output depends on your platform, but it can be something like the following:
Error: result out of range
For completeness, we show two alternative ways of reporting an error string for the current errno. They use, respectively, strerror() from <cstring> (take care: this function is not thread-safe!) and std::perror() from <cstdio>. The following two lines print a message similar to the earlier code:
Failure Handling <exception>
std::uncaught_exception()
If, anywhere in your code, you want to know whether an exception is currently in flight that has not been caught yet—in other words, detect that stack unwinding is in progress—use
uncaught_exception()
, which returns true if that is the case.
Note
There usually is no reason or safe way to use uncaught_exception(), so we advise against using it. It is just mentioned here for completeness.
std::terminate()
If exception handling fails for any reason—for example, an exception is thrown but never caught—then the runtime calls std::terminate(), which calls the terminate handler. The default handler calls std::abort(), which in turn aborts the application without performing any further cleanup. The active terminate handler is managed using the following functions from <exception>, where std::terminate_handler is a function pointer type and must point to a void function without arguments:
std::terminate_handler std::set_terminate(std::terminate_handler) noexcept
std::terminate_handler std::get_terminate() noexcept
One use case for a custom terminate handler is to automatically generate a process dump when std::terminate() is called. Having a dump file to analyze aids tremendously in tracking down the bug that triggered the process to terminate(). You should consider setting this up for any professional application.
std::unexpected()
The runtime calls std::unexpected() if a dynamic exception specification3 is disregarded: that is, if a function throws something it is not allowed to. Analogous to terminate(), this function calls an std::unexpected_handler function that can be managed using std::set_unexpected() / get_unexpected(). The default handler calls std::terminate().
Note
Both dynamic exception specifications and std::unexpected() have been deprecated and are only mentioned here for completeness.
Footnotes
1
A mixin is a class that provides some functionality to add to other classes (in this case, the capability of storing a pointer to a nested exception and some related functions). In C++, mixins are generally implemented through multiple inheritance.
2
std::exp() only sets errno for implementations where math_errhandling defined in <cmath> contains MATH_ERRNO: see Chapter 1. This appears to be mostly the case, though.
3
A dynamic exception specification is part of the function declaration and specifies, with a comma-separated list, which exceptions that function is allowed to throw. For example: ReturnType Func(...) throw(exception1, exception2, ...);.