Skip to content

Copy and Move Semantics

Copying creates an independent value according to a type's contract. Moving allows a new object to reuse resources from an object that is no longer needed in its current state.

Special member functions

A class can have a destructor, copy constructor, copy assignment, move constructor, and move assignment. User-declaring some of them affects implicit generation of others. Prefer the Rule of Zero by composing resource-managing members.

class document {
public:
    document(std::string title, std::vector<std::string> lines)
        : title_{std::move(title)}, lines_{std::move(lines)} {}

private:
    std::string title_;
    std::vector<std::string> lines_;
}; // compiler-generated copy and move operations are appropriate

std::move does not move

std::move(expression) casts its argument to an xvalue, enabling overload resolution to select move operations. The selected constructor or function performs the actual transfer.

std::string source{"data"};
std::string destination = std::move(source);

After a move, standard-library objects are generally valid but in an unspecified state unless a stronger contract is stated. They may be destroyed or assigned; do not assume they are empty.

Copy elision

Returning a local value is normally clear and efficient:

std::vector<int> sequence() {
    std::vector<int> result{1, 2, 3};
    return result;
}

Guaranteed copy-elision cases and named return-value optimization avoid many copies. Writing return std::move(result); can inhibit NRVO and is usually not an optimization.

Resource-owning type

If a class owns a non-RAII handle directly, the Rule of Five prompts review of all special operations. A common policy is movable but not copyable:

class file_handle {
public:
    file_handle(file_handle const&) = delete;
    file_handle& operator=(file_handle const&) = delete;
    file_handle(file_handle&& other) noexcept;
    file_handle& operator=(file_handle&& other) noexcept;
    ~file_handle();
};

Move operations should leave both objects destructible and assignable. Marking a move constructor noexcept when truthful enables containers to preserve strong exception guarantees during reallocation.

Forwarding

Perfect forwarding preserves the caller expression's value category through a generic wrapper using forwarding references and std::forward. Use it only in generic forwarding code; indiscriminate forwarding obscures ownership and overload selection.

Exercises

  1. Explain why std::move on a const object often selects copying.
  2. Design copy and move contracts for a unique socket handle.
  3. Explain why moved-from does not generally mean empty.