← Back to context

Comment by dsnr

8 hours ago

In simpler terms

1. You must implement a move constructor or a move assignment operator in order for std::move to do anything

2. The moved object could be left in an unusable state, depending on your implementation, after stealing its internal resources.

I never understood move semantics until I learned Rust. Everything is move by default and the compiler makes sure you never leave things in an unusable state.

This was a difficult mental hurdle to get over with Rust, but once you do, move semantics make a lot more sense.

edit: When I said everything is move by default, I mean everything that isn't "Copy", such as integers, floats, etc.

  • What Rust loses with that decision is the ability to program the "semantics" in move semantics. Rust has no distinction between hypothetical place constructor and value constructor.

    • I sure don't miss the footguns and raw boilerplate that is having a copy constructor, move constructor, copy assignment operator, move assignment operator, and destructor, per class.

      Yes, you should avoid the manual memory management that necessitates writing them. But work with a team of developers fresh out of school and next thing you know your codebase will be brimming with this kind of busywork.

    • A loss of functionality, but arguably a good thing, e.g. moving will never throw an exception/panic so you don't need an equivalent to is_nothrow_move_constructible

> You must implement a move constructor or a move assignment operator in order for std::move to do anything

Bit of a nitpick, but there are sometimes other functions with overloads for rvalue references to move the contents out - think something like std::optional's `value() &&`. And you don't necessarily need to implement those move constructor/assignment functions yourself, typically the compiler generated functions are what you want (i.e. the rule of 5 or 0)

> The moved object could be left in an unusable state, depending on your implementation, after stealing its internal resources.

The "proper" semantics are that it leaves the object in a valid but unspecified state. So, invariants still hold, you can call functions on it, or assign to it.

  • > you can call functions on it

    Only functions with no preconditions, unless the type makes more guarantees as to the moved-from state.

    • The guarantees is that a moved-from state is in an otherwise valid state.

      So, you can do things like check if a moved from std::vector is empty (often the case in practice), then start appending elements to it.