CERT.EXCEPTION.SAFETY.ASSIGN_ORDER
代入演算子の例外安全性を確保する
CERT.EXCEPTION.SAFETY.ASSIGN_ORDER は CERT C++ 規則 ERR56-CPP を実装しています。例外をスローする可能性のある操作を実行する前に、オブジェクトの状態を変更するコピー代入演算子およびムーブ代入演算子を検出します。オブジェクトが部分的に変更された後に例外が発生した場合、オブジェクトは不整合な状態のままとなり、代入操作で基本的な例外安全性を確保できなくなる可能性があります。
軽減と防止
代入演算子において例外安全性を維持するには、例外をスローする可能性のあるすべての操作が正常に完了するまで、オブジェクトの状態を変更しないようにします。一般的なアプローチとしては、まずリソースの割り当てや、例外をスローする可能性のあるその他の操作を実行し、それらの操作が正常に完了した後にのみ、オブジェクトのメンバを更新する方法があります。
もう一つの推奨されるアプローチは、コピーアンドスワップイディオムです。ソースオブジェクトの一時コピーを作成し、例外をスローしない swap() 操作を使用して、その内容をターゲットオブジェクトと交換します。一時オブジェクトの作成中に例外が発生しても、ターゲットオブジェクトは変更されません。
どちらのアプローチも現実的でない場合は、例外がスローされたときに状態変更をロールバックできるようにしてください。代入演算子は、例外が発生したかどうかに関わらず、オブジェクトを有効かつ一貫性のある状態に保つ必要があります。
脆弱コード例
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;
}
配列は、例外が発生する可能性のある割り当てが行われる前に解放され、null に設定されます。new[] が例外をスローした場合、配列はすでに nullptr となっており、nElems もすでに更新されています。オブジェクトは、一貫性を欠いた、部分的に更新された状態のまま残されます。
修正コード例 1 (割り当て優先の順序変更)
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;
}
修正コード例 2 (コピーアンドスワップイディオム、推奨)
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
注: 引数を 2 つ取るフリー関数形式 (swap(*this, tmp); / std::swap(*this, tmp);) も、同様に規格に準拠していると認識されます。
関連チェッカー
- CXX.EMPTY.CATCH
-
CERT.CONC.LOCK.NO_RELEASE_ON_EXCEPTION