Move operations (move constructor, move assignment operator) are about efficiently transferring resource ownership. When transferring resources
from the source, you don’t have to allocate any memory or perform any other operation that might fail. This is why most people will expect move
operations to be non-throwing.
Additionally, if a move operation fails, the source object can have been partially altered by the move, making recovery very tricky or just
impossible. Therefore, to ensure robustness, some functions (for instance, std::move_if_noexcept
, used by std::vector
) will
decide to copy your object if its move operations are not decorated with noexcept
. This can significantly slow down your program.
If you can not implement your move operations so that they never throw, you may only provide copy operations that will be safer to use.
Swap operations are similar to move operations in that they should be equivalent to moving two objects into each other. So if you add a swap
function to your type, it should be noexcept
too.
Note that you should not write your move operations for most classes but rely on the "Rule-of-Zero" (S3624).
This rule raises an issue when a move or swap operation is not noexcept
, which can happen in two cases:
- The operation is user-defined and is not unconditionally declared as
noexcept
,
- The operation is implicitly defined, and one of the class’s base classes or member variables does not have
noexcept
move
operations.
Noncompliant code example
struct A {
A (A const &a);
A (A && a); // Noncompliant
~A();
A &operator=(A const &a);
A &operator=(A &&a); // Noncompliant
};
void swap(A& a1, A& a2); // Noncompliant
Compliant solution
struct A {
A (A const &a);
A (A && a) noexcept;
~A();
A &operator=(A const &a);
A &operator=(A &&a) noexcept;
};
void swap(A& a1, A& a2) noexcept;