From: Isaac Dupree (isaacdupree_at_[hidden])
Date: 2008-08-04 20:08:24
a = b;
, we can have exception-safe assignment if we have, or can
simulate, nothrow move of either a's current type (move a to
backup on stack first, then try copying b; if it fails, move
a back from the stack, if it succeeds, destruct a-on-stack)
or b's current type (copy b to stack first, and if that
succeeds, destruct a and move the copy of b that's on the
stack to a).
The current implementation of variant tries to detect
whether that nothrow move exists for a. It does for builtin
types. If it does, then it uses a completely safe
implementation. If it doesn't, it uses an implementation
that still has a useful property:
1. If the type has nothrow copy-construction, we are
completely safe, whether or not Boost.Variant can detect it.
2. It doesn't need nothrow copy-construction *in general*,
it only needs to be able to copy-construct again something
that was copy-constructed the first time, in particular
type a_copied(a); //may throw
a.~type(); //it's in variant, so must be nothrow
new (&a) type(a_copied) //we wish this to never throw
3. guaranteeing that with "interesting" types is prone to
4. many types, such as std::vector, use dynamic memory
allocation, which in theory (and maybe in some real
situations) may throw.
However, things may not be as bad as they look. If a type
has nothrow swap and a nothrow constructor (e.g.
default-constructor) then we can simulate nothrow swap.
However std::vector appears to have no nothrow constructors.
So we're actually stuck even if just two of the types in
the container are standard-container types. (although,
arguably the default-constructor+swap is less likely to
throw, so it could be attempted if it seems worth the
complexity/impossibility of detecting
default-constructibility, possibly as a fallback after
copy-construction has thrown or vice versa)
We can also simulate "move" by copying bits around, but that
solution was rejected. After all, they would have to be
bits representing a's type or b's type, and we don't have a
lot of guarantees about that type. Types for which it's
absolutely definitely safe to copy bits around, have nothrow
copy-construction anyway. Any further hacks in this area
really need to go in a Boost.Move library.
Also, if we're willing to put up with possible variant
contents on the heap when the type's copy-constructor threw,
and in the hopeful policy-based future when we can actually
say we want Variant's heap-behavior rather than the
nothrow-default-construction behavior, we can get the
type-preserving strong-exception-guarantee invariant,
without any C++0x features.
Looking to the future, why doesn't the draft C++0x
standard's type-traits include:
With has_nothrow_move_constructor, we could implement the
strong exception guarantee for assignment for all Variants
that had at most one type that lacked a nothrow
move-constructor. Without it, we can't be quite as smart,
and could only guarantee strong exception safety for
assignment when *all* the variant's types have nothrow
move-construction. But if all the variant's types have
nothrow move, then the variant itself has nothrow move. Did
anyone come up with a type that legitimately has a
move-constructor that can throw (even when the destructor
can't throw, per Variant's existing requirements)?