Featured image of post The State Of `moved-from` Objects In C++

The State Of `moved-from` Objects In C++

What can you do with `moved-from` objects

Intro

There are a lot of misconceptions about the state of moved-from objects. Some people are scared of them, and believe they are invalid and it’s better not to touch them. Others think that such objects are left in “valid but unspecified state”, but I claim that this interpretation is misleading and incomplete.

Okay, let’s see what the standard says about moved-from objects:

source: https://eel.is/c++draft/lib.types.movedfrom

Standard says: “unless otherwise specified, they are in valid but unspecified state”. But what exactly does it mean? How can an object be in a valid and unspecified state at the same time?

In another place it’s well explained:

source: https://eel.is/c++draft/defns.valid

Which basically means that “valid but unspecified” is about preserving the invariants of an object. In other words you can call any function on an object that does not have any preconditions. Which makes sense - you can always call .size() on std::vector, but you should not try getting the 5th element: vec[4] if you don’t know that vector has five elements.

Otherwise specified case

The first quote from standard had an important gotcha: “unless otherwise specified”. What are those cases when “something else” is specified?

std::unique_ptr

There are a lot of cases when the class author specifies the state of moved-from objects, but let’s give a couple of examples from the C++ Standard Library. One of the most notable ones is std::unique_ptr. The standard specifies behavior of its move constructor as follows:

source: https://eel.is/c++draft/unique.ptr#single.ctor-15

This move constructor refers to its source object by u, and its postcondition specifies that calling u.get() will yield nullptr. Therefore the state of moved-from object of type std::unique_ptr is specified.

std::thread

Another such example is std::thread:

source: https://eel.is/c++draft/thread.thread.constr#10

It says that moved-from object x of type std:thread has the following property: x.get_id() == id(). What does it actually mean? What is id()?

Standard explains it at another place:

source: https://eel.is/c++draft/thread.thread.id#5

Which basically means that if you have an object of type thread::id which has its id equal to id(), then it’s not connected to any underlying thread of execution. Which together with previous paragraph means that moved-from std::thread is not associated with any underlying thread. Therefore the state of moved-from object of type std::thread is specified.

To sum up: the general rule is that moved-from objects are in valid but unspecified state, unless their post-move state is explicitly specified like in case of std:unique_ptr or std::thread.

But I said in the beginning that moved-from objects are NOT in “valid but unspecified” state. So what’s the deal?

Rule for “moved-from” objects

Let’s look at my first citation from standard, along with some hints:

Do you see it now? The rule about “valid but unspecified state” only refers to types from the C++ standard library.

Does the standard say anything about post-move semantic state of user-defined types? It does not. Therefore I might now answer the question from the title: THE STATE OF MOVED-FROM OBJECT IS NOT SPECIFIED BY STANDARD AND IT FULLY DEPENDS ON THE CLASS AUTHOR.

I often see this phrase “valid but unspecified state” being thrown around like it was a universal rule. I think this is some overgeneralization and it’s not necessarily correct.

For example: Raymond Chen in his blog “What should the state of a moved-from object be?” says:

The language specifies that a moved-from object is in a legal but indeterminate state.

Andres Fertig in his CppCon 2022 talk Back to Basics: C++ Move Semantics is claiming:

such an object(moved-from) is in a valid, yet unknown state

Jon Kalb in his CppCon 2024 talk Moved-from Objects in C++ also says that calling destructor and assignment operator must be safe on move-from objects. And while I agree that this is a sensible thing to require from your types, especially if you need to use them in STL containers, this is not a requirement of C++ language.

These statements are often presented without emphasizing that they describe the guarantees provided by the C++ standard library rather than the language itself. Maybe I’m taking their words out of the context when they mean just types from the C++ standard library, but in my opinion it’s important to distinguish the requirements that the language itself imposes from those of the C++ standard library.

Practicalities

The standard is one thing, writing sensible C++ and following good practices is another. And while it’s true that your moved-from state can be whatever you want, there are some requirements if you need to use your type inside the C++ standard library.

For example std::vector requires that your type is Erasable. This means that its destructor should run just fine, without any undefined behavior. If a move constructor leaves the object in a state where its destruction would result in undefined behavior, then the type does not satisfy Erasable.

There are also more restrictions for specific member functions of std::vector. While the language does not constrain the moved-from state of objects, using them in standard containers may impose additional requirements. For example std::vector<T>::erase() requires T to be MoveAssignable.

Conclusions

The C++ standard does not define semantic state for moved-from objects for user-defined types. It only guarantees that the object still has a valid lifetime. The “valid but unspecified state” is a library-level guarantee and does not apply as a general language rule to all user-defined types. It is often presented as a general rule of C++ move semantics. It isn’t.