What you'll learn
C++ types can behave like independent values, exclusive owners, or deliberately shared handles. Copy and move operations encode that policy. The safest design is usually the Rule of Zero: compose members that already know how to copy, move, and clean themselves up.
By the end, you'll be able to:
- Identify copy/move construction and copy/move assignment
- Explain std::move, valid moved-from states, and noexcept moves
- Apply the Rules of Zero, Five, and Three
- Design predictable value semantics without double ownership
The special member functions
Construction creates a new object; assignment changes an existing object. Copy operations read from an lvalue source, while move operations may transfer resources from an expiring non-const source. A destructor completes the set that controls value lifetime.
| Operation | Typical declaration | When used |
|---|---|---|
| Copy constructor | T(const T&) | New object from an existing value |
| Copy assignment | T& operator=(const T&) | Existing object receives a value |
| Move constructor | T(T&&) | New object takes transferable state |
| Move assignment | T& operator=(T&&) | Existing object releases then takes state |
| Destructor | ~T() | Object lifetime ends |
Copying and independent values
A correct copy has independent value behavior: changing the copy does not unexpectedly mutate the original. Standard containers and strings perform deep value copies of their elements, so a class composed from them usually receives correct generated copy operations.
#include <iostream>
#include <string>
#include <vector>
struct Playlist {
std::string name;
std::vector<std::string> tracks;
};
int main() {
Playlist original{"Focus", {"Dawn", "Flow"}};
Playlist copy{original};
copy.tracks.push_back("Night");
std::cout << original.tracks.size() << ' '
<< copy.tracks.size() << '\n';
}Key idea
Moving and transfer
A move operation may transfer expensive resources instead of duplicating them.std::move does not move by itself; it casts an expression to an rvalue so move overloads become eligible. The selected constructor or assignment performs the transfer.
#include <iostream>
#include <string>
#include <utility>
#include <vector>
int main() {
std::vector<std::string> pending{"parse", "compile", "test"};
std::vector<std::string> active{std::move(pending)};
std::cout << active.size() << '\n';
pending.clear(); // valid: its exact old contents are unspecified
pending.push_back("package");
std::cout << pending.front() << '\n';
}Watch out
Rule of Zero
If a class does not directly manage a raw resource, define none of the five special operations. Use values, containers, strings, and smart pointers as members and let their behavior compose. This removes code, reduces exception-safety work, and keeps ownership visible.
#include <string>
#include <vector>
class Document {
public:
Document(std::string title, std::vector<std::string> lines)
: title_{std::move(title)}, lines_{std::move(lines)} {}
const std::string& title() const { return title_; }
private:
std::string title_;
std::vector<std::string> lines_;
};
// No destructor, copy operation, or move operation is needed.Rule of Five and exclusive owners
A type that directly owns a raw resource often needs a destructor and deliberate copy/move policy. If copying has no sensible meaning, delete it and support moves. A move should leave the source safe to destroy and should usually be noexcept so containers can use it while preserving strong guarantees.
#include <cstdio>
#include <utility>
class FileHandle {
public:
explicit FileHandle(std::FILE* file = nullptr) : file_{file} {}
~FileHandle() { if (file_) std::fclose(file_); }
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
FileHandle(FileHandle&& other) noexcept
: file_{std::exchange(other.file_, nullptr)} {}
FileHandle& operator=(FileHandle&& other) noexcept {
if (this != &other) {
if (file_) std::fclose(file_);
file_ = std::exchange(other.file_, nullptr);
}
return *this;
}
private:
std::FILE* file_{};
};Tip
unique_ptr plus a custom deleter. Then the wrapper can often return to the Rule of Zero.Assignment and exception safety
Copy assignment must handle self-assignment, release the old value, and avoid losing it if creating the replacement throws. Copy-and-swap first creates a complete copy, then swaps it into place; the temporary later destroys the old state. It is simple and strongly safe, though a specialized assignment may reuse capacity more efficiently.
class Buffer {
public:
friend void swap(Buffer& left, Buffer& right) noexcept {
using std::swap;
swap(left.data_, right.data_);
swap(left.size_, right.size_);
}
Buffer& operator=(Buffer replacement) {
swap(*this, replacement);
return *this;
}
private:
int* data_{};
std::size_t size_{};
};
// The full type must also define construction, copying, and destruction.Recap & quick check
Key takeaways
- Construction creates a new object; assignment replaces the value of an existing object.
- std::move enables move overload resolution but the selected operation performs the transfer.
- Moved-from objects remain valid, though their exact values may be unspecified.
- The Rule of Zero is the preferred outcome when standard members already manage resources.
- Direct resource owners need explicit copy/move policy and usually noexcept move operations.
Quick check
1. Which operation initializes a new T from an existing lvalue T?
2. What does std::move do directly?
3. What is the Rule of Zero?
4. Why mark a move constructor noexcept when true?
Next: Module 12 — Operator Overloading & Conversions, where value types gain natural syntax without surprising their users.