First, there are different levels of
exception guarantees, specifying how involved objects behave in the presence of exceptions:
- strong - every object is always in a semantically valid state
- basic - every object is still destructible and doesn't evoke UB
- none - no guarantees
There are several
idiomatic ways to implement the special member functions with C++11, to sum up:
1. Rule Of FiveOverload copy/move ctor, copy/move op= and dtor.
This is the "stupid" approach which requires a big amount of boilerplate, code duplication and thus is rather prone to errors when extending a class.
2. Copy-and-SwapOverload all 5 special methods + swap(). Use swap() in copy/move op=.
Avoids some code duplication, but still a lot of boilerplate.
Achieves strong exception guarantee (each object is always in a valid state).
3. Rule of Four-and-a-halfOverload copy/move ctor and dtor as usual.
Overload op= taking a value (not const- or rvalue-reference), which means it acts as both move and copy assignments.
4. Rule of ZeroLet the compiler generate the Big Five.
Code complexity wise, this is the best solution, as the behavior is clear and no member can be forgotten. It does not work when there are complex ownership rules, or custom copy logic (for handles).
I've found my smart pointer
aurora::CopiedPtr to be a very nice fit for pointers which require deep copies. It can easily reduce the number of methods needed from 5 to 0. For example, in
Thor I need it for quite a few things.
Downside: with compiler-generated member-wise copy, the exception guarantee is often reduced to basic (when the copy operation aborts in the middle).
I'm wondering if strong exception guarantee is something we really have to go for in SFML. Generally, I'm more in favor of readable/compact code. Having to implement the Big Five for every 2nd class is also often an indication of bad ownership design or lack of internal abstraction (too many types have custom ownership rules).