Rule A15-0-2 (required, architecture / design / implementation,partially automated)
At least the basic guarantee for exception safety shall be provided for all operations. In addition, each function may offer either the strong guarantee or the nothrow guarantee
Rationale
Exceptions introduce additional data flow into a program. It is important to consider all the effects of code taking such paths to always recover from an exception error properly and always preserve object’s invariants. “Well-designed functions are exception safe, meaning they offer at least the basic exception safety guarantee (i.e., the basic guarantee). Such functions assure callers that even if an exception is thrown, program invariants remain intact (i.e., no data structures are corrupted) and no resources are leaked. Functions offering the strong exception safety guarantee (i.e., the strong guarantee) assure callers that if an exception arises, the state of the program remains as it was prior to the call.” [effective modern c++] The C++ standard library always provides one of the following guarantees for its operations, the same needs to be followed by code compliant to the guidelines. “ Basic guarantee for all operations: The basic invariants of all objects are maintained, and no resources, such as memory, are leaked. In particular, the basic invariants of every built-in and standard-library type guarantee that you can destroy an object or assign to it after every standard-library operation Strong guarantee for key operations: in addition to providing the basic guarantee, either the operation succeeds, or it has no effect. Nothrow guarantee for some operations: in addition to providing the basic guarantee, some operations are guaranteed not to throw any exception.
” [C++ Programming Reference] Nothrow means in this context that the function not only does not exit with an exception, but also that internally an exception cannot occur.
Example
//% $Id: A15-0-2.cpp 289436 2017-10-04 10:45:23Z michal.szczepankiewicz $
#include <cstdint>
#include <cstring>
class C1
{
public:
C1(const C1& rhs)
{
CopyBad(rhs);
// Non-compliant if an exception is thrown, an object
// will be left in
an indeterminate state
CopyGood(rhs); // Compliant - full object will be properly copied or
// none of its properties will be changed
}
~C1() { delete[] e; }
void CopyBad(const C1& rhs)
{
if (this != &rhs)
{
delete[] e;
e = nullptr;
// e changed before the block where an exception can
// be thrown
s = rhs.s;
// s changed before the block where an exception can be
// thrown
if (s > 0)
{
e = new std::int32_t[s]; // If an exception will be thrown
// here, the
// object will be left in an indeterminate
// state
}
std::memcpy(e, rhs.e, s * sizeof(std::int32_t));
}
}
void CopyGood(const C1& rhs)
{
std::int32_t* eTmp = nullptr;
if (rhs.s > 0)
{
eTmp = new std::int32_t[rhs.s]; // If an
exception will be thrown
// here, the
// object will be left unchanged
std::memcpy(eTmp, rhs.e, rhs.s * sizeof(std::int32_t));
}
delete[] e;
e = eTmp;
s = rhs.s;
}
private:
std::int32_t* e;
std::size_t s;
};
class A
{
public:
A() = default;
};
class C2
{
public:
C2() : a1(new A), a2(new A) // Non-compliant - if a2 memory allocation
// fails, a1 will never be deallocated
{
}
private:
A* a1;
A* a2;
};
class C3
{
public:
C3() : a1(nullptr), a2(nullptr) // Compliant
{
try
{
a1 = new A;
a2 = new A;
// If memory allocation for a2 fails, catch-block will
// deallocate a1
}
catch (...)
{
delete a1;
a1 = nullptr;
delete a2;
a2 = nullptr;
throw;
}
}
private:
A* a1;
A* a2;
};
See also
SEI CERT C++ [10]: ERR56-CPP. Guarantee exception safety