CERT.EXCEPTION.SAFETY.ASSIGN_ORDER
Guarantee exception safety in assignment operators
CERT.EXCEPTION.SAFETY.ASSIGN_ORDER implements CERT C++ rule ERR56-CPP. It detects copy and move assignment operators that modify an object's state before performing an operation that might throw an exception. If an exception occurs after the object has been partially modified, the object can be left in an inconsistent state and the assignment operation may fail to provide basic exception safety.
Mitigation and prevention
To maintain exception safety in assignment operators, avoid modifying an object's state until all operations that can throw have completed successfully. A common approach is to perform resource allocation and other potentially throwing operations first, and update the object's members only after those operations succeed.
Another recommended approach is the copy-and-swap idiom. Create a temporary copy of the source object and then swap its contents with the target object using a non-throwing swap() operation. If an exception occurs while creating the temporary object, the target object remains unchanged.
If neither approach is practical, ensure that any state changes can be rolled back if an exception is thrown. Assignment operators should leave the object in a valid, consistent state regardless of whether an exception occurs.
Vulnerable code example
Buffer& Buffer::operator=(const Buffer &rhs) {
delete[] array;
array = nullptr; // mutation of *this
nElems = rhs.nElems; // mutation of *this
array = new int[nElems]; // CERT.EXCEPTION.SAFETY.ASSIGN_ORDER — may throw, after mutation
std::copy(rhs.array, rhs.array + nElems, array);
return *this;
}
The array is released and nulled before the possibly-throwing allocation. If new[] throws, array is already nullptr and nElems already updated. The object is left in an inconsistent, partially-updated state.
Fixed code example 1 (allocate-first reordering)
Buffer& Buffer::operator=(const Buffer &rhs) {
int *tmp = new int[rhs.nElems]; // possibly-throwing step happens first
delete[] array; // *this is mutated only after tmp has succeeded
array = tmp;
nElems = rhs.nElems;
std::copy(rhs.array, rhs.array + nElems, array);
return *this;
}
Fixed code example 2 (copy-and-swap idiom, preferred)
Buffer& Buffer::operator=(const Buffer &rhs) {
Buffer tmp(rhs); // may throw, but *this is untouched so far
swap(tmp); // non-throwing; *this and tmp are a symmetric no-fail commit
return *this;
} // tmp's (old *this's) resources released on scope exit
Note: the two-argument free-function form (swap(*this, tmp); / std::swap(*this, tmp);) is equally recognized as compliant.
Related checkers
- CXX.EMPTY.CATCH
-
CERT.CONC.LOCK.NO_RELEASE_ON_EXCEPTION