Destruction and restoration has a fundamentally different behavior from copying and replacing. For comparison:
- Destroy the old value. He disappeared forever.
- Try creating a new value.
- If necessary, discard and create a default value.
Copy and replace:
- Try creating a new value.
- Discard the old value if necessary.
- Apply new value.
Both have their merits, but copying and swap are ubiquitous, so its flaws are discussed on the principle of least surprise. Therefore, let him emulate his behavior:
foo & operator = ( foo const & other ) { static_assert ( std::is_nothrow_move_constructible< foo >::value || std::is_nothrow_default_constructible< foo >::value , "Exception safety violation" ); foo next( other ); try { this-> ~ foo(); new (this) foo( std::move( next ) ); } catch (...) { new (this) foo(); throw; } return * this; }
Although it is more complex, it is better than throwing a swap , which after an exception can leave a mishmash of old and new values.
In the general case, when the move constructor does not throw (you remember its noexcept , right?), The algorithm nicely terminates:
foo & operator = ( foo const & other ) { foo next( other ); // The dangerous part is over now. this-> ~ foo(); new (this) foo( std::move( next ) ); return * this; }
source share