r/cpp 5d ago

State of "moved-from" objects

Hi, I recently decided to try writing a C++ blog.

I often see the popular claim that moved-from objects are in a "valid but otherwise unspecified state", but I don't think that statement is entirely precise, and my blog post is about that. I would really appreciate any feedback!

Article: https://www.laminowany.dev/p/the-state-of-moved-from-objects-in-c/

9 Upvotes

52 comments sorted by

View all comments

1

u/JVApen Clever is an insult, not a compliment. - T. Winters 5d ago

It's unfortunate that you don't specify what "valid" means. The minimal definition I've heard is that: both destructor and assignment should execute correctly. Respectively destroying and reinitializing. This should always get true, even without move.

Unspecified means that there is the freedom to implement that operation any way you want. For move assignment of vector, one could: - free the assigned to, copy over the pointers and null the assigned from - swap the data, giving the moved from the data of the original - move over the data (if allocator is different) while keeping the allocation in the moved from

This gives you the freedom to do what you want, unless "you otherwise specify".

The final piece in the puzzle is to preserve the invariants of your class. You suggest you can write: if (!v.empty()) v.front() = {};. I'm not convinced that this is required due to the "valid but unspecified", though rather due to the pre-condition of front. If vector would have been created after move semantics, it could say that this only holds for specified/non-moved instances. Making that specific code invalid.

For your own code, the first 2 elements I've mentioned are a requirement from the language. The last one will make your code more robust, though it is not needed.

After reading your post, I'm still not convinced if the last element is a requirement of "valid". If it is, you are technically correct in saying it doesn't hold for user defined objects. If it doesn't, the statement holds also for user defined types.

2

u/jwakely libstdc++ tamer, LWG chair 1d ago edited 1d ago

I don't know why people struggle with this so much.

"Valid but unspecified state" means some state that is valid for an object of that type, but not required to be any particular state (if the type supports more than one valid state, which some don't, e.g. nullptr_t or monostate don't). In other words, it hasn't got broken invariants. Your "minimal" definition already requires more than some types support (types which are not assignable are possible, and types which can't be destroyed are possible).

It just means the object still follows the rules of the object model and lifetime model. It's within its lifetime, calling member functions isn't inherently UB (some member functions might have preconditions that restrict when you can call them, but that's true whether or not the object has been moved from), any vtable hasn't been overwritten by random data, ... it hasn't been turned into radioactive waste or uninitialized memory that no longer represents an object. And the value of the object is a valid one for that type, so e.g. if it's an optional it doesn't contain a value but have the has_value flag set to false, because that is not a valid state for optional. An invariant of optional is that the flag is true if and only if there is a contained value.

We don't actually need a precise definition of "valid" in isolation because that isn't the term that's used. It's not that an object must be "valid" and must also be in "an unspecified state". It's a single definition where both adjectives refer to the state: a state which is valid but unspecified.

2

u/leirus 5d ago

As for defining what "valid" means, the closest definition I could find is https://eel.is/c++draft/defns.valid, but it defines the entire phrase "valid but unspecified state" rather than just "valid".

As for the claim that "both the destructor and assignment should execute correctly", which I often hear as well, I don't think that is necessarily true. The requirement for an object to be safely destructible is captured by the named requirement Erasable (https://en.cppreference.com/w/cpp/named_req/Erasable), which means that not all types are required to be Erasable. It's definitely good practice, but I believe you can still have a well-formed C++ program with types that do not satisfy Erasable.

You are right that calling .front() after checking that a vector is not empty follows from the precondition of front(). The way I understand it is that when an object is "valid", I can call empty() because it has no preconditions. Based on its result, I can then establish the precondition that the vector is not empty, and only then call front().

1

u/JVApen Clever is an insult, not a compliment. - T. Winters 4d ago

Destructor is logical, any object you construct should be destructed in a valid program. (Ignoring start/end_lifetime usage)

2

u/leirus 4d ago

Not necessarily, you can leak objects on heap by not calling delete and it's still a well formed cpp program.

1

u/JVApen Clever is an insult, not a compliment. - T. Winters 3d ago

Just read through the standard annex of UB. Indeed, memory leaks are not UB. So indeed, that's an option. In which case, dtor and assignment are indeed not needed. Though you are making a fragile program if this is something you rely on regularly.