C++ Move Semantics: std::move vs Copying
Understand C++ copy and move semantics in simple steps: what std::move actually does, moved-from objects, const sources and safe reuse without empty-state assumptions.
What you will learn
- Describe copying and moving as different operation contracts.
- Explain why std::move alone performs no transfer.
- Avoid relying on an unspecified moved-from value.
- Recognize that moving from a const source may select a copy.
- Restore a known source state before reading its elements.
A copy makes another value; a move allows a different way to build it
Suppose a vector owns a large sequence of readings. A copy creates another vector with the same reading values, so both vectors can be used independently. Sometimes the program no longer needs the source's old contents and wants another object to receive the result instead. A move-enabled operation may be able to reuse resources rather than copy every element. The word may matters: the type and selected operation decide what actually happens.
Think of copying the contents of a folder versus giving another person a folder you no longer need. This analogy helps explain intent, but it is not a rule about the old C++ object's exact contents. Some types have a precise empty-source promise; the unique_ptr lesson showed one. For ordinary library types without a more specific promise, plan around a valid but unspecified moved-from state rather than inventing an empty result.
The source object itself still exists after a move operation. It has not automatically been destroyed, renamed or removed from scope. Its destructor will still run at the appropriate lifetime boundary. You may use operations whose requirements are satisfied, such as assigning a new value. Do not immediately read its first vector element merely because the first element existed before the move. The contents may have changed in a way your code must not assume.

std::move changes an expression's use, not the object by itself
Include <utility> for std::move. It produces an expression that can let an appropriate move operation be selected. Calling std::move on a variable does not, by itself, copy memory, free storage or clear that variable. Another operation must use the expression. In auto destination = std::move(source), construction of destination is the operation that may use the source as a move source.
There are technical names for expression categories, including lvalues and xvalues. You do not need to memorize all of them to follow this lesson. The practical question is whether the destination has a suitable operation for the supplied expression. If it does, that operation defines the effects. If not, a copy or a compilation error may result depending on the type and available functions. std::move is not a command that forces every possible object to move.
The first example copies a vector and then move-constructs another vector from the source. We report values from the destinations, where the contract is useful, and do not print an assumed source size. Then we assign a fresh one-element sequence to the source and safely read that known element. This order makes the program's behavior independent of any particular implementation's moved-from vector contents.
Copy, move, then establish a known source value
The copy and moved destination are separate values. The source is explicitly assigned a new sequence before front() is used.
Predict the output, edit the source, then select Run code.
Compare with the starter's expected output
Copy first: 9
Moved first: 2
Reused source: 7
After editing inputs, calculate a new prediction; this reference describes only the starter.
Follow the objects, not just the std::move spelling
source initially owns two integers. copied is created by copying those values, so it owns its own sequence. moved is then created using the move-enabled vector construction. Changing copied's first value afterwards does not change moved's first value, because copied already had its own integers. Finally, assign replaces the source's current state with the explicitly requested one-element sequence. Its first element is now known to be seven.
Notice what this example does not test: it does not insist that source.size() was zero immediately after the move, does not save an element reference and assume every move preserves it, and does not count hidden allocations. Those would require more specific guarantees than the general lesson needs. State the facts required by your program rather than collecting observations and promoting them into rules for all C++ types.
Returning an owning value from a function is normal modern C++. Do not automatically add std::move to every return statement. The language can avoid some copies when returning local values, and an unnecessary cast can interfere with an optimization opportunity. Begin with a clear return-by-value interface. Investigate costs only when needed, using evidence rather than assuming that adding the word move always makes a program faster.
- The copy is made while source still contains the original data.
- The move-enabled construction creates another destination from source.
- The copied destination can be edited independently.
- A fresh assignment gives source a known state before element access.
Const can prevent the move operation you expected
Moving resources often requires changing the source's representation. A usual move constructor takes a non-const rvalue reference for that reason. Applying std::move to a const vector does not remove const. In the second example, the normal copy constructor can accept the const source, so constructing another vector works by copying instead of performing the usual resource-transfer move. The source remains available with its original two values.
This does not mean const is bad. Const is useful when you intend to keep a baseline value unchanged. The mistake is promising a transfer while making the source unavailable for the operation that requires mutation. Decide whether the interface needs observation, an independent copy or a transfer. Then make the type and call agree with that intention instead of removing const everywhere merely to chase a hypothetical optimization.
A move is not universally constant-time, allocation-free or unable to throw. Member types, allocators and the selected operation can affect its behavior. You do not need to master all those details today; simply avoid writing a blanket performance claim. For the small bounded lab, correctness is the goal. Keep using owning library members and the Rule of Zero rather than writing custom move functions for ordinary records without a demonstrated need.
See why std::move does not remove const
The source is const. This vector construction uses copying, so both objects have the two expected values; the code does not claim that every use of std::move transfers resources.
Predict the output, edit the source, then select Run code.
Compare with the starter's expected output
Baseline count: 2
Another count: 2
After editing inputs, calculate a new prediction; this reference describes only the starter.
Make your test independent of the moved-from contents
The lab reads a count from zero through five, followed by that many whole integer lines in minus one hundred through one hundred. Reject malformed, missing or out-of-range required input as Invalid input with status one. Collect all values before making the copy and moved destination. Extra lines after the requested sequence are outside this single-report protocol and remain unread.
After copying the accepted sequence, construct the moved destination using std::move(source). Assign source a new sequence containing ninety-nine, append minus one to the copied destination, and print the copy count, moved count, reused source value and complete moved sequence. Do not print an assumed old source size or read its elements before assignment. The empty input sequence is valid and produces an empty Moved values line.
Test zero elements, the five-element maximum, repeated readings, negative readings and both numeric endpoints. Check every moved value, not just its count. As a repair exercise, remove the source assignment in a separate copy of the program and identify why a later front() call would need a new proof. Do not execute that unproven access. Restore the assignment and explain the difference between knowing an object is valid and knowing that it contains a first element.
Compile one program and compare its output
The lesson editors run complete programs only when the compiler sandbox is available. You can also save one example as main.cpp and use your installed compiler. Run only the repaired version of an intentional compile-error example.
macOS / Linux (choose your compiler):
clang++ -std=c++20 -Wall -Wextra -Wpedantic main.cpp -o lesson
# Or: g++ -std=c++20 -Wall -Wextra -Wpedantic main.cpp -o lesson
./lesson
Windows Developer Command Prompt:
cl /std:c++20 /EHsc /W4 main.cpp /Fe:lesson.exe
lesson.exeIndependent lab
Copy and move a bounded vector, change the copied destination, and reuse the source only after assigning a known value.
Acceptance checklist
- Read a whole count in 0..5 and that many integer lines in -100..100; bad or missing input prints Invalid input and returns 1.
- Copy source before constructing a moved destination from it.
- Assign source the known sequence {99} before accessing its first element.
- Append -1 only to the copied destination.
- Print Copy count, Moved count, Reused source and all Moved values without assuming the original moved-from contents.
Need a hint?
- The original count may be zero; report a range rather than reading its first element.
- The moved-from source remains an object, so assignment can establish a new known value.
- Check the copied count grows while the moved destination preserves the accepted sequence.
Write and run your lab
Build your own solution before revealing the reference. Use the input field for normal, boundary and invalid cases; this runner executes your program but does not grade it.
Predict the output, edit the source, then select Run code.
Your lab evidence and notes
Record predictions, actual output, commands and the cause of each repair. Code is plain text: this scratchpad does not execute or grade C++.
Loading local notes…
Review your acceptance checklist and quiz explanations before marking this lesson complete. This is self-review, not a grade or certification.
Reveal the explained lab solution
Reference lab solution
Compare the reference with your own reasoning. Running this solution does not grade or complete the lesson.
Predict the output, edit the source, then select Run code.
Expected output
Copy count: 3
Moved count: 2
Reused source: 99
Moved values: 2 4
The copied destination owns independent values before the source is used for a move operation.
A fresh assignment gives the source a known nonempty sequence, so front() is justified without relying on an unspecified moved-from value.
Complete destination output checks data preservation, while count zero demonstrates that valid objects and nonempty objects are different conditions.
Review before moving on
Explain the lesson's objectives in your own words, compare your lab evidence with the acceptance checklist, and keep the first failing case alongside your repair. Completing a page is not proof that every C++ feature or input is supported.
- Describe copying and moving as different operation contracts.
- Explain why std::move alone performs no transfer.
- Avoid relying on an unspecified moved-from value.
- Recognize that moving from a const source may select a copy.
- Restore a known source state before reading its elements.
References and further reading
Original examples and exercises accompany these primary references. Tool-specific diagnostics and support depend on your installed version.