CERT.EXCEPTION.SAFETY.ASSIGN_ORDER

Guarantee exception safety in assignment operators

This checker is applicable only to the modern engine.

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

Copy
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)

Copy
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)

Copy
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