When to Make a Type Non-Movable in C++11

When to make a type non-movable in C++11?

Herb's answer (before it was edited) actually gave a good example of a type which shouldn't be movable: std::mutex.

The OS's native mutex type (e.g. pthread_mutex_t on POSIX platforms) might not be "location invariant" meaning the object's address is part of its value. For example, the OS might keep a list of pointers to all initialized mutex objects. If std::mutex contained a native OS mutex type as a data member and the native type's address must stay fixed (because the OS maintains a list of pointers to its mutexes) then either std::mutex would have to store the native mutex type on the heap so it would stay at the same location when moved between std::mutex objects or the std::mutex must not move. Storing it on the heap isn't possible, because a std::mutex has a constexpr constructor and must be eligible for constant initialization (i.e. static initialization) so that a global std::mutex is guaranteed to be constructed before the program's execution starts, so its constructor cannot use new. So the only option left is for std::mutex to be immovable.

The same reasoning applies to other types that contain something that requires a fixed address. If the address of the resource must stay fixed, don't move it!

There is another argument for not moving std::mutex which is that it would be very hard to do it safely, because you'd need to know that noone is trying to lock the mutex at the moment it's being moved. Since mutexes are one of the building blocks you can use to prevent data races, it would be unfortunate if they weren't safe against races themselves! With an immovable std::mutex you know the only things anyone can do to it once it has been constructed and before it has been destroyed is to lock it and unlock it, and those operations are explicitly guaranteed to be thread safe and not introduce data races. This same argument applies to std::atomic<T> objects: unless they could be moved atomically it wouldn't be possible to safely move them, another thread might be trying to call compare_exchange_strongon the object right at the moment it's being moved. So another case where types should not be movable is where they are low-level building blocks of safe concurrent code and must ensure atomicity of all operations on them. If the object value might be moved to a new object at any time you'd need to use an atomic variable to protect every atomic variable so you know if it's safe to use it or it's been moved ... and an atomic variable to protect that atomic variable, and so on...

I think I would generalize to say that when an object is just a pure piece of memory, not a type which acts as a holder for a value or abstraction of a value, it doesn't make sense to move it. Fundamental types such as int can't move: moving them is just a copy. You can't rip the guts out of an int, you can copy its value and then set it to zero, but it's still an int with a value, it's just bytes of memory. But an int is still movable in the language terms because a copy is a valid move operation. For non-copyable types however, if you don't want to or can't move the piece of memory and you also can't copy its value, then it's non-movable. A mutex or an atomic variable is a specific location of memory (treated with special properties) so doesn't make sense to move, and is also not copyable, so it's non-movable.

Make a class non-copyable *and* non-movable

As others already mentioned in the comments, deleted constructors was introduced in C++11. To answer your question, the following rules hold in general:

  1. The two copy operations are independent. Declaring copy constructor does not prevent compiler to generate copy assignment and vice versa. (Same as in C++98)
  2. Move operations are not independent. Declaring either one of them prevents the compiler to generate the other. (Different from copy operations.)
  3. If any of the copy operations is declared, then none of the move operation will be generated. (Your case.)
  4. If any of the move operation is declared, then none of the copy operation will be generated. This is the opposite rule of the previous.
  5. If a destructor is declared, then none of the move operation will be generated. Copy operations are still generated for reverse compatibility with C++98.
  6. Default constructor generated only when no constructor is declared. (Same as in C++98)

As requested in the comments, here are some sources (C++11 is draft N3242):

  • Copy operations: § 12.8.8, § 12.8.19
  • Move operations: § 12.8.10, § 12.8.21
  • Default constructor: § 12.1.5

Is it possible to return an instance of a non-movable, non-copyable type?

If that compiles, it is a bug in the compiler. VC2015 correctly fails to compile it.

class Foo
{
public:
Foo() {}
Foo(const Foo&) = delete;
Foo(Foo&&) = delete;
};

Foo Bar()
{
return Foo();
}

Gives me:

xxx.cpp(327): error C2280: 'Foo::Foo(Foo &&)': attempting to reference a deleted function

and g++ 4.9 says:

error : use of deleted function 'Foo::Foo(Foo&&)'

The standard is very clear that a copy constructor or move constructor must exist and be accessible, even if RVO means it is not invoked.

C++ container with non-copyable non-movable element type

Here's a simple, yet incomplete solution under the assumption that each element is constructed with the same arguments. It uses placement new to construct the elements in-place (see also this SO question):

#include <cstdlib>
#include <utility>
#include <new>

// sample structure, non-copyable, non-moveable, non-default-constructible
struct Foo
{
Foo() = delete;
Foo(const Foo&) = delete;
Foo& operator = (const Foo&) = delete;
Foo(Foo&&) = delete;
Foo& operator = (Foo&&) = delete;

Foo(int a, char b, double c) : m_a(a), m_b(b), m_c(c) { }

int m_a;
char m_b;
double m_c;
};

template <typename T>
struct MyArray
{
// Array ctor constructs all elements in-place using the
// provided parameters
template <typename... Args>
MyArray(std::size_t sz, Args&&... args)
: m_sz(sz),
m_data(static_cast<T*>(malloc(sz * sizeof(T))))
{
for (std::size_t i=0; i<m_sz; ++i)
{
new (&m_data[i]) T(std::forward<Args>(args)...);
}
}

~MyArray()
{
for (std::size_t i=0; i<m_sz; ++i)
{
m_data[i].~T();
}
free(m_data);
}

std::size_t m_sz;
T *m_data;
};

int main()
{
Foo foo(1, '2', 3.0);
std::size_t s = 5;
MyArray<Foo> foo_arr(s, 1, '2', 3.0);
}

Note that a few things are missing:

  • This basic implementation will leak memory if an exception is thrown inside MyArray's constructor.
  • You will probably want an iterator implementation, begin()/end() operators etc., for more convenience and to get the same behaviour as provided by the standard containers.
  • For illustration's sake I also didn't bother with proper encapsulation. You should probably make m_sz and m_data private members.

C++ std::unique_ptr but non movable?

Just make objs non-copyable and non-moveable, as in:

struct objs
{
objs(objs const&) = delete;
objs& operator=(objs const&) = delete;
objs& operator=(objs&&) = delete;
objs(objs&&) = delete;

std::unique_ptr< obj > obj_a;
std::unique_ptr< obj > obj_b;
std::unique_ptr< obj > obj_c;

objs()
: obj_a(std::unique_ptr< obj >( new obj("object a") ))
,obj_b(std::unique_ptr< obj >( new obj("object b") ))
,obj_c(std::unique_ptr< obj >( new obj("object c") ))
{}

};

Plus, once you've done that, there is no need for pointers internally either. You can simplify the code as:

struct objs
{
objs(objs const&) = delete;
objs& operator=(objs const&) = delete;
objs& operator=(objs&&) = delete;
objs(objs&&) = delete;

obj obj_a;
obj obj_b;
obj obj_c;

objs()
: obj_a("object a")
,obj_b("object b")
,obj_c("object c")
{}

};

Using a non-copyable, non-movable type of member variable in a template class

Rather than taking an instance of the object to create, you can construct it inside your object by instead taking parameters of the type you wish to construct your class with. This will not work directly with std::atomic_flag due to the limitations of initializing with ATOMIC_FLAG_INIT (until C++20, that is), but would be fine with something like std::atomic<bool>.

For example:

template <typename T>
struct MyClass : foo {
MyClass() = delete;

// list of parameters to pass to value's constructor
// if this is only going to be with primitive-like types,
// you could take Args by value instead of forwarding reference
template <typename... Args>
explicit MyClass(int id, Args&&... args)
: foo(id), value(std::forward<Args>(args)...)
{}

T value;
};

Then you should be able to do

MyClass<std::atomic<bool>> bar{0, false};

why should all iterators / iterator adaptors not-movable in C++11?

That post, from a year before the standard was ratified, is outdated. The poster is Daniel Krügler, an active committee member, and it's a bit of political lobbying:

These aren't moveable and probably some more by accident, because the
rules for implicitly generated move operations became clarified just
at the Pittsburgh meeting. A general library issue has been opened

http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#1331

to cope for lack of move support in the library. Be sure that you
contact you national body representative, because I've heart that this
issue is not a sufficient replacement for a national body comment
against the FCD.

In other words, all those types being non-movable would be a showstopper bug for the standard, and he wants readers in the Usenet audience to demand that the problem be fixed before the standard becomes official.

The defect has been moved to the "closed" list. The resolution is (link provided for convenience):

Review the library portion of the spec and incorporate the newly added core feature Move Special Member Functions (N3044).

Since N3044 is a hefty bit of material, it's easy to see why it would be essential for such basic functionality to work.

Iterators, and anything else with simple value semantics like std::duration and std::time_point, are certainly movable. As others have mentioned, copyability implies movability, and if it didn't the language would be broken. This post wasn't wrong at the time; rather it's arguing about the brokenness of the unfinished language.



Related Topics



Leave a reply



Submit