This rule is part of MISRA C++:2023.
Usage of this content is governed by Sonar’s terms and conditions. Redistribution is
prohibited.
Rule 18.4.1 - Exception-unfriendly functions shall be noexcept
[support.start.term] Implementation-defined 9.1
Category: Required
Analysis: Decidable,Single Translation Unit
Amplification
The following functions are considered as exception-unfriendly and are required to be implicitly or explicitly noexcept:
- Any function or constructor directly called (explicitly or implicitly) to initialize a non-
constexpr, non-local variable with
static or thread storage duration;
- All destructors;
- All copy-constructors of an exception object;
- All move constructors;
- All move assignment operators;
- All functions named "swap";
Additionally, the arguments passed to extern "C" functions std::set_terminate, std::atexit or
std::at_quick_exit shall be convertible to function pointers to noexcept functions.
This rule does not apply to any member function defined as = delete.
Rationale
When an exception is thrown, the call stack is unwound up to the point where the exception is to be handled. The destructors for all automatic
objects declared between the point where the exception is thrown and where it is to be handled will be invoked. If one of these destructors exits with
an exception, then the program will terminate in an implementation-defined manner. Requiring destructors to be noexcept and
enforcing M23_201: MISRA C++ 2023 Rule 18.5.1 ensures that std::terminate does not get called, as required by
M23_202: MISRA C++ 2023 Rule 18.5.2.
Exceptions from destructors are also undesirable for objects that are at non-local scope or that are declared static, as they are
destroyed in a "close-down" phase after main terminates. There is nowhere within the code that a handler can be placed to catch any
exception that may be thrown, leading to a call to std::terminate. Similarly, non-local objects may be constructed before
main starts, meaning that any exception thrown during their construction cannot be caught.
Most destructors are noexcept by default, meaning that the omission of an explicit noexcept-specifier is generally
compliant.
Note: this rule does not apply to the constructors of classes used to construct local objects with static storage duration, as
these are constructed the first time their owning function is called (i.e. after main has started), allowing exceptions thrown by them to
be caught.
When an exception is thrown, the exception object is copy-initialized from the operand of the throw-expression. If an exception is thrown
during this copy, this is the exception that will be propagated, which may surprise developers. Furthermore, if a catch handler catches by
value (which is prohibited by M23_196: MISRA C++ 2023 Rule 18.3.2), another copy-initialization will happen. If this throws, the
program will terminate. It is therefore better to ensure that exception objects' copy constructors do not throw.
Functions named "swap" are conventionally used as customization points for std::swap. The C++ Standard Library containers and
algorithms will not work correctly if swapping of two elements exits with an exception.
Non-throwing "swap" functions are also important when implementing the strong exception safety guarantee in a copy (or move) assignment operator.
Similarly, move constructors and move assignment operators are usually expected to be non-throwing. If they are not declared noexcept,
strong exception safety is more difficult to achieve. Furthermore, algorithms may choose a different, possibly more expensive, code path if move
operations are not noexcept.
Functions passed as arguments to extern "C" functions are likely to be invoked from C code that is not able to handle exceptions.
The C++ Standard states that if a function registered using std::atexit or std::at_quick_exit is called and exits with an
exception, then std::terminate is called. The C++ Standard requires that a terminate handler set via std::set_terminate must
not return to its caller, including with an exception (see [terminate.handler]).
Example
class C1
{
public:
C1(){} // Compliant - never used at non-local scope
~C1(){} // Compliant - noexcept by default
};
class C2
{
public:
C2(){} // Not noexcept - see declaration of c2 below
C2( C2 && other ) {} // Non-compliant - move constructor
C2 & operator=( C2 && other ); // Non-compliant - move assignment
~C2() noexcept( true ) {} // Compliant
friend void swap( C2 &, C2 & ); // Non-compliant - function named swap
};
C2 c2; // Non-compliant - construction is non-local
class C3
{
public:
C3(){} // Compliant - c3 in foo not in non-local scope
~C3() noexcept( false ) {} // Non-compliant
};
class MyException : public std::exception // Non-compliant - implicit copy
{ // constructor is noexcept( false )
public:
MyException ( std::string const & sender ); // Rule does not apply
const char * what() const noexcept override;
std::string sender;
};
void foo()
{
static C3 c3; // Compliant - constructed on first call to foo
throw MyException( "foo" );
}
void exit_handler1(); // Non-compliant - passed to atexit
void exit_handler2() noexcept; // Compliant - also passed to atexit
int main()
{
try
{
const int32_t result1 = std::atexit( exit_handler1 );
const int32_t result2 = std::atexit( exit_handler2 );
C1 c1;
foo(); // Any exception thrown will be caught below
}
catch ( ... ) {}
}
extern "C"
{
void f( void( *func )() );
}
f( [](){} ); // Non-compliant - function passed to extern "C"
Copyright The MISRA Consortium Limited © 2023