Rule A15-5-1 (required, implementation, automated)

All user-provided class destructors, deallocation functions, move constructors, move assignment operators and swap functions shall not exit with an exception. A noexcept exception specification shall be added to these functions as appropriate.

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 or delete operators exits with an exception, then the program will terminate in an implementation-defined manner. Move constructors and move assignment operators are usually expected to be nonthrowing. If they throw exceptions, strong exception safety cannot be guaranteed, because the original type values could be already modified or partially modified. Note that some operations in the standard library statically check the noexcept specification of the move constructors and move assignment operators of parameter types. They may choose less efficient algorithms or provide fewer exception safety guarantees if these are not noexcept.

The standard-library containers and algorithms will not work correctly if swapping of two elements exits with an exception. A non-throwing swap function is also an important basic tool to implement the strong exception safety guarantee in a copy assignment operator (see A12-8-2). Note that it is acceptable for a destructor or deallocation function to throw an exception that is handled within this function, for example within a try-catch block. Note that deallocation functions are declared noexcept by default. A destructor is declared as noexcept by default unless a destructor of any base class or member is potentially-throwing. Using a base class or member with a potentially-throwing destructor is a violation of this rule. The respective base class or member destructor must be fixed in order to comply to this rule. The intention of this rule is that the implementation of a user-provided destructor is ensured to not exit with an exception. Only then, the default noexcept specification added implicitly to the user-provided destructor is correct. It may be explicitly restated as noexcept for documentation purposes. The compiler also adds a noexcept specification implicitly for any defaulted special member function. This noexcept specification depends on the noexcept specification of the member and base class operations that the defaulted special member function will call implicitly. It is therefore not required to default a special member function only to add the noexcept specification. Reasons to default a special member function exist independently from this rule, for example due to A12-0-1.

Exception

Generic move constructors, generic move assignment operators, and generic swap functions may have noexcept specifications which depend on type properties of the template parameters.

Example

//% $Id: A15-5-1.cpp 309720 2018-03-01 14:05:17Z jan.babst $
#include <stdexcept>
#include <type_traits>

class C1
{
public:
C1() = default;

// Compliant - move constructor is non-throwing and declared to be noexcept
C1(C1&& rhs) noexcept {}

// Compliant - move assignment operator is non-throwing and declared to be
// noexcept
C1& operator=(C1&& rhs) noexcept { return *this; }

// Compliant - destructor is non-throwing and declared to be noexcept by
// default

~C1() noexcept {}
};

void Swap(C1& lhs,
C1& rhs) noexcept // Compliant - swap function is non-throwing and
// declared to be noexcept
{
// Implementation
}

class C2
{
public:
C2() = default;

// Compliant - move constructor is non-throwing and declared to be noexcept
C2(C2&& rhs) noexcept
{
try
{
// ...
throw std::runtime_error(
"Error"); // Exception will not escape this function
}

catch (std::exception& e)
{
// Handle error
}
}

C2& operator=(C2&& rhs) noexcept
{
try
{
// ...
throw std::runtime_error(
"Error"); // Exception will not escape this function
}

catch (std::exception& e)
{
// Handle error
}
return *this;

}

// Compliant - destructor is non-throwing and declared to be noexcept by
// default
~C2()
{

try
{
// ...
throw std::runtime_error(
"Error"); // Exception will not escape this function

}

catch (std::exception& e)
{
// Handle error
}
}
};

// Non-compliant - swap function is declared to be noexcept(false)
void Swap(C2& lhs, C2& rhs) noexcept(false)
{
// ...
// Non-compliant - Implementation exits with an exception
throw std::runtime_error("Swap function failed");
}

class C3
{
public:
C3() = default;
C3(C3&& rhs) // Non-compliant - move constructor throws
{
// ...
throw std::runtime_error("Error");
}
C3& operator=(C3&& rhs) // Non-compliant - move assignment operator throws
{
// ...
throw std::runtime_error("Error");
return *this;

}
~C3() // Non-compliant - destructor exits with an exception
{
throw std::runtime_error("Error");
}
static void operator delete(void* ptr, std::size_t sz)

{
// ...
throw std::runtime_error("Error"); // Non-compliant - deallocation
// function exits with an exception
}
};

void Fn()
{

C3 c1; // program terminates when c1 is destroyed
C3* c2 = new C3;

// ...
delete c2; // program terminates when c2 is deleted
}

template <typename T>
class Optional
{
public:
// ...

// Compliant by exception
Optional(Optional&& other) noexcept(
std::is_nothrow_move_constructible<T>::value)
{
// ...
}

// Compliant by exception
Optional& operator=(Optional&& other) noexcept(
std::is_nothrow_move_assignable<T>::value&&
std::is_nothrow_move_constructible<T>::value)
{
// ...
return *this;

}

// ...
};

See also

MISRA C++ 2008 [7]: 15-5-1: A class destructor shall not exit with an exception. HIC++ v4.0 [9]: 15.2.1: Do not throw an exception from a destructor C++ Core Guidelines [11]: E.16: Destructors, deallocation, and swap must never fail C++ Core Guidelines [11]: C.85: Make swap noexcept ISO/IEC 14882:2014 [3]: 15.4: [except.spec] ISO/IEC 14882:2014 [3]: 20.2.4, paragraph 9: [forward] A12-0-1 in section 6.12.0 A12-8-2 in section 6.12.8 Rule A15-5-2 (required, implementation, partially automated) Program shall not be abruptly terminated. In particular, an implicit or

explicit invocation of std::abort(), std::quick_exit(), std::_Exit(), std::terminate() shall not be done.

Rationale

Functions that are used to terminate the program in an immediate fashion, i.e. std::abort(), std::quick_exit(), std::_Exit(), do so without calling exit handlers or calling destructors of automatic, thread or static storage duration objects. It is implementation-defined whether opened streams are flushed and closed, and temporary files are removed. The std::terminate() function calls std::abort() implicitly in its terminate handler, and it is implementation-defined whether or not stack unwinding will occur. Note: std::terminate_handler shall not be used.

Example

//% $Id: A15-5-2.cpp 289436 2017-10-04 10:45:23Z michal.szczepankiewicz $
#include <cstdlib>
#include <exception>
void F1() noexcept(false);
void F2() // Non-compliant
{
F1(); // A call to throwing f1() may result in an implicit call to
// std::terminate()
}
void F3() // Compliant
{
try
{
F1(); // Handles all exceptions from f1() and does not re-throw
}
catch (...)
{
// Handle an exception
}
}
void F4(const char* log)

{
// Report a log error
// ...
std::exit(0); // Call std::exit() function which safely cleans up resources
}
void F5() // Compliant by exception
{
try
{
F1();
}
catch (...)
{

F4("f1() function failed");
}
}
int main(int, char**)

{
if (std::atexit(&F2) != 0)
{
// Handle an error
}

if (std::atexit(&F3) != 0)
{
// Handle an error
}

// ...
return 0;
}

See also

MISRA C++ 2008 [7]: 15-5-3 (Required) The terminate() function shall not be called implicitly. HIC++ v4.0 [9]: 15.3.2 Ensure that a program does not result in a call to std::terminate SEI CERT C++ [10]: ERR50-CPP. Do not abruptly terminate the program