Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I mark a classes move-operation noexcept if it contains a standard container?

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 elems 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.

like image 428
towi Avatar asked Jan 23 '14 22:01

towi


People also ask

Is default constructor a Noexcept move?

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 ...

Why should move constructor be Noexcept?

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)


1 Answers

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.

like image 148
Howard Hinnant Avatar answered Oct 22 '22 01:10

Howard Hinnant