6

Consider a classic approach to make a class non copyable:

// similar to boost::noncopyable
class noncopyable
{
protected:
    constexpr noncopyable() = default;

    noncopyable(const noncopyable&) = delete;
    noncopyable& operator=(const noncopyable&) = delete;
};

class c: private noncopyable
{ /* ... */ };

As declaring any copy operation prevents auto-generation of move operations, this automatically makes all derived classes non moveable (by default) also (so full name of noncopyable would be noncopyable_and_nonmoveable).

Now let's look for classic noncopyable classes from the standard library, e.g. unique_ptr. It's noncopyable but moveable, otherwise its utility would be limited. I suppose same is true for many other cases.

Actually I cannot come up with any example when a class should be made non moveable. My argument is: even if a class is not planned to be moved there's a little chance this would be done by a mistake that can cause a harm.

It's actually two related questions:

1) Why boost::noncopyable is non moveable also? This causes issues, consider:

struct c: private boost::noncopyable
{
    std::unique_ptr<Something> something;

    c(c&&) = default;
    c& operator=(c&&) = default;
};

doesn't work (wandbox) - move operations cannot be generated because they are not generated for the base class. You need to implement them manually -> list your members -> maintenance -> bugs. Just defaulting move operations in noncopyable would solve the problem.

2) What are use cases when moveability is harmful so it should be prevented?

Andriy Tylychko
  • 15,967
  • 6
  • 64
  • 112
  • 1
    I've made Singleton's non-moveable. It's arguable though. – AndyG Oct 10 '17 at 11:55
  • @AndyG: why? what mistakes did you try to prevent? – Andriy Tylychko Oct 10 '17 at 11:56
  • There was probably a better way, my memory escapes me a bit. But essentially these were Singletons implemented with with "get_instance()" function, so basically a normal class with a static member and static `get_instance`. Should be impossible to construct/copy in any other way, but to enforce that I deleted the copy/move related functions. – AndyG Oct 10 '17 at 12:00
  • 1
    Some design patterns also encourage that objects, once created, are completely immutable. This means that references to them are always valid. Any "mutator" function simply produces a new object that lives in a pool somewhere. These should also be non-movable. – AndyG Oct 10 '17 at 12:02
  • This should really have been split into two questions. – einpoklum Aug 11 '22 at 11:53

3 Answers3

8

Non-moveable gives you more control over object identity.

If an object is moveable, the address of it can change:

moveonly a;
b = std::move(a);

Now, anyone how still has a reference to a points to a stale object or (something else entirely).

Other situations where this arises is when you have external logic depending on object identity ("address stability") if you will, like e.g. with pthread mutexes: Move constructor for std::mutex

Addendum - The naming convention scoped_XXXX is oft used to imply non-moveable types (i.e. types guaranteeing immutable instance identity). Contrast this with e.g. unique_XXXX which implies a unique instance, that may be moved around. Note however, the standard library did not adopt this naming convention in full: Example: std::lock_guard<> vs std::unique_lock<>. C++17 amends this a bit

sehe
  • 374,641
  • 47
  • 450
  • 633
  • object identity aside, I'd also mention object scope; for example, making std::scoped_guard ( or any scoped_whatever ) movable would be a plain lie ... – Massimiliano Janes Oct 10 '17 at 13:01
  • @MassimilianoJanes rather, indeed, `scoped_XXXX` seems to be library-convention to name classes that promise to have fixed identity/reference stability (contrasting with `unique_XXXX` which can move). Let me think about wording that briefly. – sehe Oct 10 '17 at 13:08
2

1) Why boost::noncopyable is non moveable also?

Very most likely, historical reasons. boost::noncopyable is supposed to work with pre-C++11 compilers. There's actually very little reason to inherit from boost::noncopyable when using C++11 and beyond. If it were moveable in C++11, how do we establish the same move semantics in C++03 compilers?

2) What are use cases when moveability is harmful so it should be prevented?

non-moveablity should apply to kinds of resources that are supposedly given to you be an external system - Such as synchronization primitives, objects that represents stuffs like your computer's ACPI platform, and more.

WhiZTiM
  • 21,207
  • 4
  • 43
  • 68
  • I can move a `std::ifstream` just fine IIRC – sehe Oct 10 '17 at 12:26
  • @sehe. Of cause we can. But I am [talking about](http://coliru.stacked-crooked.com/a/4f792a012aca2f64) global objects like `std::cout`, `std::cerr`, `std::clog`, `std::cin`. Imagine the horror if someone moved their *resource* away somewhere deep in code. :-). – WhiZTiM Oct 10 '17 at 12:33
  • What's the horror? http://coliru.stacked-crooked.com/a/ed8aaba1beafd86b – sehe Oct 10 '17 at 12:52
  • @sehe... I wouldn't be happy if a library I'm using, somewhere in its code path, replaces the streambuf of `std::cout` and fails to restore it. – WhiZTiM Oct 10 '17 at 12:58
  • I mean, it's not protecting anything then. I'm not saying anything normatively, just noting that `moved-from` `std::ostream` is basically equivalent – sehe Oct 10 '17 at 12:59
  • @sehe.. Ahh! Ok. I understand. I will reword my answer. Thank you :-) – WhiZTiM Oct 10 '17 at 13:00
  • 2
    Ironically, actually moving from `std::cout` has _less_ effect (apparently it just shares the buffer): http://coliru.stacked-crooked.com/a/48e0f936425853b5 – sehe Oct 10 '17 at 13:04
0

Why boost::noncopyable is non moveable also?

From the C++11 standard:

If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared as defaulted if and only if

— X does not have a user-declared copy constructor,

— X does not have a user-declared copy assignment operator,

— X does not have a user-declared move assignment operator,

— X does not have a user-declared destructor, and

— the move constructor would not be implicitly defined as deleted.

So, I guess that when you delete your copy c'tor and/or copy assignment, you prevent the declaration of a default move constructor.

What are use cases when moveability is harmful so it should be prevented?

The first thing that comes to my mind is objects that depend on they location in the memory space, such as mutexes.

Daniel Trugman
  • 8,186
  • 20
  • 41