The idiomatic way to implement move-operations on classes with a standard container member can not be noexcept
and therefore will not be movable by operations like vector.push_back()
. Or am I mistaken?
To get speed from
vector<Elem> data;
// ...
data.push_back( elem );
We are encouraged to make out move operations noexcept
-- so during the vectors resize the library can safely move elements to the reallocated storage.
class Elem {
// ...
Elem(Elem&&) noexcept; // noexcept important for move
Elem& operator=(Elem&&) noexcept; // noexcept important for move
};
So far so good, now my elem
s can be pushed-back much faster.
But: If I add a container as member, can my class be still be marked noexcept-move? All standard containers do not have their move noexcept
!
class Stuff {
vector<int> bulk;
// ...
Stuff(Stuff&& o) // !!! no noexcept because of vector-move
: bulk(move(o.bulk))
{}
Stuff& operator=(Stuff&&) // !!! no noexcept...
{ /* appropriate implementation */ }
};
This also means, that we can also not rely on the compiler-generated move-operations, right? The following complete class will also not have noexcept
-move-operations and therefore not be "fast", correct?
struct Holder {
vector<int> bulk;
};
Maybe vector<int>
is a bit too simple to move, but what about vector<Elem>
?
This would have great consequences on all data structures with containers as members.
Inheriting constructors and the implicitly-declared default constructors, copy constructors, move constructors, destructors, copy-assignment operators, move-assignment operators are all noexcept(true) by default, unless they are required to call a function that is noexcept(false) , in which case these functions are ...
noexcept is nice for two reasons: The compiler can optimize a little better because it doesn't need to emit any code for unwinding a call stack in case of an exception, and. It leads to incredible performance differences at runtime for std::vector (and other containers, too)
I feel your pain, really.
Some std::implementations will mark the move members of containers as noexcept
, at least conditional on the allocator properties, as an extension. You can adapt your code to automatically take advantage of these extensions, for example:
class Stuff {
std::vector<int> bulk;
// ...
public:
Stuff(Stuff&& o)
noexcept(std::is_nothrow_move_constructible<std::vector<int>>::value)
: bulk(std::move(o.bulk))
{}
Stuff& operator=(Stuff&&)
noexcept(std::is_nothrow_move_assignable<std::vector<int>>::value)
{ /* appropriate implementation */ }
};
And you can even test whether or not your type does have noexcept move members:
static_assert(std::is_nothrow_move_constructible<Stuff>::value,
"I hope Stuff has noexcept move members");
static_assert(std::is_nothrow_move_assignable<Stuff>::value,
"I hope Stuff has noexcept move members");
libc++ in particular does have noexcept move members for all of its containers, whenever the allocator allows, and std::allocator
always allows the container move members to be noexcept.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With