Discussion: Moving Out

Execute the code to understand the output and gain insights into the behavior of moved-from objects.

Run the code

Now, it’s time to execute the code and observe the output.

Press + to interact
#include <iostream>
#include <string>
int main()
{
std::string hello{"Hello, World!"};
std::string other(std::move(hello));
std::cout << "'" << hello << "'";
}

Understanding the output

A moved-from object should no longer be used, as resources will have been moved out of it, pointers might point to destroyed objects, and so on.

How a particular moved-from object behaves is up to the implementer of that class. The implementer has a lot of freedom here, but at a minimum, the object should be safe to destroy so nothing breaks when, for instance, a moved-from local variable goes out of scope. Unless explicitly documented, we, as a user, should not assume that a class makes any further guarantees.

Standard library guarantee

The C++ standard provides an additional guarantee for the classes in the standard library, though:

Objects of types defined in the C++ standard library may be moved from. (…) Unless otherwise specified, such moved-from objects shall be placed in a valid but unspecified state.

The std::string does not specify otherwise, so a moved-from string is placed in a “valid but unspecified state.” What does that mean, though?

The C++ standard provides the following definition:

(a) value of an object that is not specified except that the object’s invariants are met and operations on the object behave as specified for its type.

So, while we don’t know what the moved-from string contains, we at least know that .size() returns the size of whatever string is left in there—that .empty() is true if, and only if, .size() == 0, and so on. But still, the actual state of the object is unspecified, and we can’t be sure what the output will be, so it’s better to avoid using moved-from objects altogether.

Implementation of the std::string move constructor

One typical way to implement the move constructor for std::string is that the new string steals the memory from the old string to avoid copying it. It is then important that it’s not also possible to access this memory through the old string object, or modification to one string would also affect the other. It’s also important that the old string doesn’t attempt to free this memory in its destructor.

If std::string is implemented with a pointer to a string buffer on the heap, we can, for instance, imagine that the move constructor sets the old string’s buffer pointer to null. We can use similar techniques to implement move operations for our own classes.

Some implementations will optimize short strings by storing the string inside the std::string object itself instead of allocating a buffer on the heap, a technique known as short-string optimization. In these cases, it’s not possible for a move constructor to steal the memory of the old string, and a copy is needed. One reasonable, valid state for such a moved-from string would be to still contain the original string.

When creating this course, we tested this on Windows, Mac, and Linux, both with long and short strings. In all cases, they output the empty string ''. But as always, with unspecified behavior, we won’t assume this to always be true. We will only assume that the std::string invariants are met.

Note: Short-string optimization is a performance optimization technique used by some std::string implementations in C++ to improve the efficiency of handling short strings. SSO takes advantage of the fact that small strings can often be stored directly within the std::string object itself without needing to allocate separate heap memory. This is done by using a fixed-size buffer within the std::string object to store the characters of short strings.

Recommendations

Here are some recommendations to ensure safe and predictable behavior of moved-from objects.

  • Avoid using moved-from objects: After an object has been moved from, avoid using it except to destroy it. This prevents undefined behavior.

  • Maintain valid state in custom move operations: In your own classes, ensure that move constructors and move assignment operators leave the object in a valid state where class invariants are still met.

  • Ensure safe destruction: If maintaining a valid state is not feasible, at least ensure that it remains safe to call the destructor on the moved-from object.

Level up your interview prep. Join Educative to access 70+ hands-on prep courses.