[2026] C++ Shallow vs Deep Copy & Move Semantics Complete Guide [#33-2]

[2026] C++ Shallow vs Deep Copy & Move Semantics Complete Guide [#33-2]

이 글의 핵심

Master C++ copy and move semantics: shallow/deep copy, Rule of Three/Five, rvalue references, std::move, perfect forwarding, pitfalls, and production patterns.

Introduction: When Interviewer Asks “Why Do We Need Move Semantics?”

Interview-Focused Compression

C++ Practical Guide #14-1: Move Semantics and rvalue References covered move semantics concepts and usage. This article organizes in interview-frequent order: “shallow vs deep copy difference” → “copy constructor/assignment” → “why moves needed” → “rvalue references and std::move”—structured as a script you can memorize and recite.

Real Problem: “Copying Makes Program Slow”

When interviewers ask about move semantics, they’re not just testing syntax. They ask “why is it needed?” In production, these situations frequently occur:

  • Returning large vectors from functions: In pre-C++11, temporary objects had to be copied, incurring memory allocation and duplication costs.
  • push_back large objects into containers: Copy constructor calls allocate entire internal buffers and duplicate contents.
  • Classes with pointer members: Naive copying points at same memory, risking double free or dangling pointers. This article organizes causes (shallow vs deep copy) and solutions (move semantics) in interview answer format.

Concrete Problem Scenarios

Common production situations: Scenario 1: JSON Parsing Result Transfer
Passing large parsed nlohmann::json objects to multiple functions causes memory usage spikes with copy-only. Using moves passes only pointers, improving memory efficiency. Scenario 2: Thread Pool Task Queue
Copying std::function or std::packaged_task into queue copies all captured objects. std::move transfers large captured vectors/maps to queue without duplication. Scenario 3: Network Buffer Transfer
Passing received std::vector<uint8_t> buffer to parsing function causes allocation and copy proportional to packet size. Moving transfers in O(1). Scenario 4: Builder Pattern Object Assembly
Builders accumulating std::string, std::vector, etc. across multiple stages cause copies at each stage without moves when returning final object. Scenario 5: Log Message Queue
Copying std::string messages into log queue increases memory and CPU usage proportionally to log volume. std::move transfers message buffers to queue without duplication. Scenario 6: Database Result Set Transfer
Passing std::vector<Row> query results to parsing layer causes duplication of thousands of rows × dozens of columns in memory with copying. Moving transfers ownership in O(1). Scenario 7: Serialization/Deserialization Pipeline
Parsing std::string JSON to nlohmann::json, then passing internal std::map to next processing stage causes memory usage to multiply by stage count with copying at each stage.

Everyday Analogy

  • Shallow copy: Copying a photo by copying only “original file path”, so two people point at same file. If one deletes file, other sees broken link.
  • Deep copy: Duplicating photo file itself, so each has independent file. One deletion doesn’t affect other.
  • Move: Difference between “moving furniture wholesale when relocating” and “duplicating each piece of furniture for new home”. No need to duplicate objects no longer used—just transfer ownership, much faster. What this covers:
  • Shallow copy (copy pointer values only—points at same memory as original) vs deep copy (duplicate resources too): pointer-only vs resource duplication
  • Copy constructor & assignment: Rule of Three/Five (C++ convention of defining constructor, destructor, copy-related functions together)
  • Why move semantics needed: Eliminate unnecessary copies, “transfer” resources
  • rvalue references, std::move, state after move

Table of Contents

  1. Shallow vs Deep Copy
  2. Copy Constructor and Assignment
  3. Why Move Semantics Needed (Interview Script)
  4. rvalue References and std::move
  5. Perfect Forwarding
  6. Common Mistakes and Cautions
  7. Performance Comparison
  8. Best Practices
  9. Production Patterns
  10. Interview Q&A Summary

1. Shallow vs Deep Copy

Concept Visualization

Understand shallow vs deep copy difference with diagram below. 아래 코드는 mermaid를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

flowchart TB
  subgraph shallow[Shallow Copy]
    S1[Original object] -->|Copy pointer value only| S2[Copy object]
    S1 -.->|Points at same memory| M1["(Heap memory)"]
    S2 -.->|Points at same memory| M1
  end
  subgraph deep[Deep Copy]
    D1[Original object] -->|Allocate new resource & duplicate| D2[Copy object]
    D1 -.->|Points at| M2["(Heap memory A)"]
    D2 -.->|Points at| M3["(Heap memory B)"]
  end

Shallow Copy

  • Copies pointer (address) values only.
  • Copy and original point at same memory.
  • Problem: If one side deletes, other has dangling pointer, plus double-free risk. 아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class Bad {
    int* data;
public:
    Bad(int n) : data(new int[n]) {}
    ~Bad() { delete[] data; }
    // No copy constructor → compiler provides default: member-wise copy = shallow copy
};
int main() {
    Bad a(100);
    Bad b = a;  // b.data == a.data → same pointer!
    // On destruction, both a and b try freeing same memory → danger
    return 0;
}

Code explanation:

  • Bad a(100);: Allocates 100 ints on heap, data points to it.
  • Bad b = a;: Compiler-provided default copy constructor is called. This copies pointer value only like b.data = a.data.
  • Result: a.data and b.data point at same address.
  • On destruction: When a destructs first, delete[] data frees memory, then when b destructs, it tries freeing already-freed memory, causing double free bug.

Deep Copy

  • Allocates new resource (heap memory, etc.) and duplicates contents.
  • Copy has independent resource, so freeing one doesn’t affect other.
  • Trade-off: Allocation and duplication costs; must implement copy constructor and assignment yourself. 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <algorithm>
class Good {
    int* data;
    size_t size;
public:
    Good(int n) : data(new int[n]), size(n) {}
    ~Good() { delete[] data; }
    // Deep copy: allocate new memory then duplicate contents
    Good(const Good& other) : data(new int[other.size]), size(other.size) {
        std::copy(other.data, other.data + other.size, data);
    }
    Good& operator=(const Good& other) {
        if (this != &other) {
            delete[] data;
            data = new int[other.size];
            size = other.size;
            std::copy(other.data, other.data + other.size, data);
        }
        return *this;
    }
};

Detailed code explanation: Copy constructor:

  • data(new int[other.size]): Allocates new memory same size as original.
  • std::copy(...): Duplicates all data from original to new memory.
  • Result: Original and copy each have independent memory. Copy assignment operator:
  • if (this != &other): Prevents self-assignment (a = a). Without this check, self-assignment would delete[] data freeing own memory, then reference already-freed other.data.
  • delete[] data: First frees existing memory. Difference from copy constructor: constructor creates new object, so no existing resources.
  • data = new int[other.size]: Allocates new memory and duplicates contents. For interviews, saying “shallow copy only copies pointers pointing at same resource, deep copy allocates new resources creating independent copy” suffices.

Shallow vs Deep Copy Selection Criteria

  • Shallow copy suitable: Almost never. Copying only pointers makes ownership ambiguous, risking double-free and dangling pointers. For shared resources, use std::shared_ptr, etc. for safety.
  • Deep copy suitable: When copy must exist independently from original. Example: copying config object for modification without affecting original.
  • Move suitable: When “no longer using this object” is clear. Return values, objects discarded after push_back, swap, etc.

2. Copy Constructor and Assignment

Rule of Three / Five

아래 코드는 mermaid를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

flowchart LR
  subgraph rule3[Rule of Three]
    R1[Destructor]
    R2[Copy constructor]
    R3[Copy assignment]
  end
  subgraph rule5["Rule of Five (C++11+)"]
    R1
    R2
    R3
    R4[Move constructor]
    R5[Move assignment]
  end
  • Rule of Three: If you define any one of destructor, copy constructor, copy assignment operator, it means “directly managing resources,” so consider all three.
  • Rule of Five: In C++11+ with moves, include move constructor and move assignment, designing all five together (destructor, 2 copies, 2 moves). That is, classes managing heap memory with pointer members:
  • Using only default copy → shallow copy → danger.
  • So implement deep copy in copy constructor & assignment,
  • And optionally implement “transfer” resources in move constructor & assignment.

Rule of Five Implementation Example

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <algorithm>
#include <utility>
class Resource {
    int* data;
    size_t size;
public:
    Resource(size_t n) : data(new int[n]), size(n) {}
    ~Resource() { delete[] data; }
    // Copy constructor
    Resource(const Resource& other) : data(new int[other.size]), size(other.size) {
        std::copy(other.data, other.data + other.size, data);
    }
    // Copy assignment
    Resource& operator=(const Resource& other) {
        if (this != &other) {
            delete[] data;
            data = new int[other.size];
            size = other.size;
            std::copy(other.data, other.data + other.size, data);
        }
        return *this;
    }
    // Move constructor
    Resource(Resource&& other) noexcept
        : data(other.data), size(other.size) {
        other.data = nullptr;
        other.size = 0;
    }
    // Move assignment
    Resource& operator=(Resource&& other) noexcept {
        if (this != &other) {
            delete[] data;
            data = other.data;
            size = other.size;
            other.data = nullptr;
            other.size = 0;
        }
        return *this;
    }
};

Move constructor & assignment essentials:

  • data(other.data): Takes original’s pointer only. No memory allocation or duplication.
  • other.data = nullptr: When original destructs, delete[] nullptr safely does nothing, preventing double-free.
  • noexcept: Enables std::vector, etc. to use move on reallocation. Without noexcept, may use copy, degrading performance.

Rule of Five Call Scenarios

CodeFunction called
T b = a;Copy constructor
T c = std::move(a);Move constructor
b = c;Copy assignment
b = std::move(c);Move assignment
T d = f(); (f returns local T)RVO or move constructor
Creating traced struct like Traced with std::cout in each constructor/assignment lets you verify call order.

Complete Rule of Five Example: Copy-and-Swap Idiom

Example applying Copy-and-Swap idiom for exception-safe copy assignment implementation. Leverages copy constructor to reduce duplicate code while securing self-assignment safety and exception safety simultaneously. 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <algorithm>
#include <utility>
// 타입 정의
class Buffer {
    int* data;
    size_t size;
public:
    Buffer(size_t n) : data(new int[n]), size(n) {}
    ~Buffer() { delete[] data; }
    // Copy constructor
    Buffer(const Buffer& other) : data(new int[other.size]), size(other.size) {
        std::copy(other.data, other.data + other.size, data);
    }
    // Copy-and-Swap: copy assignment = copy construct + swap
    Buffer& operator=(Buffer other) {  // Receive by value → copy or move
        swap(*this, other);
        return *this;
    }
    // Move constructor
    Buffer(Buffer&& other) noexcept
        : data(other.data), size(other.size) {
        other.data = nullptr;
        other.size = 0;
    }
    friend void swap(Buffer& a, Buffer& b) noexcept {
        using std::swap;
        swap(a.data, b.data);
        swap(a.size, b.size);
    }
};

Copy-and-Swap behavior:

  • a = b (lvalue): In operator=(Buffer other), other is copy constructedswap(*this, other) exchanges contents.
  • a = std::move(b) (rvalue): other is move constructed → pointer transferred without copy → exchange with swap.
  • Self-assignment a = a: other is copy of a, so after swap, other has a’s old resources, freed on function exit. Safe.
  • Exception safety: If exception during copy construction, *this remains unchanged.

Rule of Five Checklist

Items to verify when designing resource-managing classes.

ItemImplementedNotes
DestructorFree resources with delete[] / delete, etc.
Copy constructorDeep copy (allocate new memory + duplicate)
Copy assignmentPrevent self-assignment, Copy-and-Swap recommended
Move constructorSpecify noexcept, null-initialize original
Move assignmentSpecify noexcept, free existing resources before move

3. Why Move Semantics Needed (Interview Script)

Copy vs Move Visualization

아래 코드는 mermaid를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

flowchart LR
  subgraph copy[Copy]
    C1[Original] --> C2[Duplicate data]
    C2 --> C3[Target]
    C1 -.->|Maintained| C1
  end
  subgraph move[Move]
    M1[Original] --> M2[Transfer pointer/handle only]
    M2 --> M3[Target]
    M1 -.->|Empty state| M1
  end

Interviewer: “Why do we need move semantics?”

Example answer: “To reduce unnecessary copying. For example, when returning large vectors from functions, previously we had to copy temporary objects, which was expensive. With move semantics, we transfer resources (like memory blocks) from objects ‘no longer used’. Instead of allocating new and duplicating contents like copying, we just swap pointers to transfer ownership, much faster. So in return values, std::vector::push_back(std::move(x)), etc., moves happen instead of copies, improving both performance and resource management.”

One-line Summary

  • Copy: Create another resource and duplicate contents → high cost.
  • Move: Transfer resource as-is and leave original in “empty” state → low cost. Move is “needed” to leverage that cost difference.

Practical Example 1: Vector Return

아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <vector>
// Pre-C++11: copy on return (high cost)
std::vector<int> createVector() {
    std::vector<int> vec(1000000);
    // Fill data...
    return vec;  // C++11: automatically moves (or RVO)
}
int main() {
    std::vector<int> data = createVector();  // No copy, only move
    return 0;
}

Why does C++11 move?
In return vec;, vec is local object about to be destroyed. C++11 standard automatically treats such objects as rvalue, so move constructor is selected on return. If RVO (Return Value Optimization) applies, there might be no copy or move at all; even without RVO, only move occurs.

Practical Example 2: swap Implementation (Using Move)

Swapping two objects’ contents costs more with copying for larger objects. Using moves just exchanges pointers, much faster. 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <utility>
// 실행 예제
template <typename T>
void mySwap(T& a, T& b) {
    T temp = std::move(a);  // Move a to temp
    a = std::move(b);       // Move b to a
    b = std::move(temp);    // Move temp to b
}
// Usage
#include <vector>
int main() {
    std::vector<int> v1 = {1, 2, 3};
    std::vector<int> v2 = {4, 5, 6};
    mySwap(v1, v2);  // Contents swapped (without copying)
    return 0;
}

Traditional swap (using copy) is T temp = a; a = b; b = temp; with 3 total copies. Using moves exchanges pointers 3 times, so performance difference is large when swapping big vectors or strings.

Practical Example 3: unique_ptr Move

std::unique_ptr is not copyable, only movable. Because ownership is unique. 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <memory>
#include <iostream>
int main() {
    std::unique_ptr<int> ptr1 = std::make_unique<int>(42);
    // std::unique_ptr<int> ptr2 = ptr1;  // ❌ Error: not copyable
    std::unique_ptr<int> ptr2 = std::move(ptr1);  // ✅ Move
    if (!ptr1) {
        std::cout << "ptr1 is null (ownership transferred)\n";
    }
    std::cout << "*ptr2 = " << *ptr2 << "\n";  // 42
    return 0;
}

Output: Prints ptr1 is null (ownership transferred) and *ptr2 = 42. unique_ptr becomes nullptr after move.

4. rvalue References and std::move

rvalue Reference (T&&)

  • Reference that binds only to rvalue (temporary object, return value, std::move result, etc.).
  • Expresses intent: “this value will disappear soon, so don’t copy, just take it”.
  • Move constructor and move assignment typically declared as T(T&&) and T& operator=(T&&), taking original’s resources inside and emptying original’s pointer to nullptr, etc.

std::move

  • Casting that makes named objects (lvalue) treatable as “movable value”.
  • std::move(x) tells compiler “OK to transfer x’s resources”, then move constructor/assignment is selected.
  • After move, original is left in “valid but unspecified” state per standard convention. Usually no longer used, or not used until reassignment. 아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 코드를 직접 실행해보면서 동작을 확인해보세요.
#include <vector>
#include <iostream>
int main() {
    std::vector<int> a = {1, 2, 3};
    std::vector<int> b = std::move(a);
    std::cout << "a.size()=" << a.size() << " b.size()=" << b.size() << "\n";
    return 0;
}

Output: Prints one line a.size()=0 b.size()=3. Code explanation:

  • std::move(a): Casts a to rvalue. Doesn’t actually move. Only casts.
  • b’s move constructor is called, taking a’s internal buffer.
  • After move, a becomes empty vector (size 0). std::vector guarantees empty state after move. For interviews, saying “std::move casts lvalue to rvalue enabling move operations” and “after move, don’t use original or only use after reassignment” suffices.

Adding to Container with Move

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <vector>
#include <string>
int main() {
    std::vector<std::string> names;
    std::string name = "Alice";
    // ❌ Copy: name's contents copied to vector
    names.push_back(name);
    // ✅ Move: name's internal buffer transferred to vector (name becomes empty string)
    names.push_back(std::move(name));
    return 0;
}

Why std::move Needed in rvalue Reference Parameters

Many people find this confusing. Parameters received as rvalue reference type (T&&) are named variables inside function, so they’re lvalue. Thus to actually move, must cast to rvalue again with std::move. 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <vector>
class Container {
    std::vector<int> data;
public:
    // vec is rvalue reference type, but inside function vec is lvalue!
    Container(std::vector<int>&& vec) : data(std::move(vec)) {}
    //                                      ^^^^^^^^^^^^^^^^
    //                                      Without std::move, copy occurs!
};
int main() {
    std::vector<int> temp = {1, 2, 3};
    Container c(std::move(temp));  // temp → vec transfer, vec → data move
    return 0;
}

Core rule: rvalue reference type means “receives movable value”, but named variables are always lvalue. To actually cause move in initializer lists or assignments, must use std::move. This rule is also why std::forward is used in Perfect Forwarding.

lvalue vs rvalue Overloading

Overloading same function with T& and T&& selects different versions depending on whether passed value is lvalue or rvalue. This enables branching like “lvalue = copy, rvalue = move”. 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iostream>
#include <string>
void process(const std::string& s) {
    std::cout << "lvalue (copyable): " << s << "\n";
}
void process(std::string&& s) {
    std::cout << "rvalue (movable): " << s << "\n";
}
int main() {
    std::string a = "Hello";
    process(a);           // lvalue (copyable): Hello
    process(std::move(a)); // rvalue (movable): Hello
    process("World");     // rvalue (movable): World (literal is rvalue)
    return 0;
}

5. Perfect Forwarding

Why Perfect Forwarding Needed

In wrapper functions passing arguments to internal functions, must maintain “lvalue as lvalue, rvalue as rvalue” property to prevent unnecessary copies. Receiving by value always causes copy; using only rvalue reference can’t receive lvalue. Universal reference (T&&) with std::forward handles both cases at once.

Problem and Solution

Receiving by value makes arg lvalue inside function, so even passing rvalue, process(arg) always calls only lvalue overload. Using T&& and std::forward maintains original property. 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iostream>
#include <string>
#include <utility>
// 실행 예제
void process(const std::string& s) { std::cout << "lvalue: " << s << "\n"; }
void process(std::string&& s) { std::cout << "rvalue: " << s << "\n"; }
// ✅ T&&: deduces as lvalue reference if lvalue, rvalue reference if rvalue (universal reference)
template <typename Arg>
void goodWrapper(Arg&& arg) {
    process(std::forward<Arg>(arg));  // Maintain original property
}
int main() {
    std::string text = "Hello";
    goodWrapper(text);              // lvalue: Hello
    goodWrapper(std::string("Hi"));  // rvalue: Hi ✅
    goodWrapper("World");           // rvalue: World (literal is rvalue)
    return 0;
}

std::forward vs std::move: std::move always casts lvalue to rvalue. std::forward maintains original lvalue/rvalue property when passing arguments in template wrappers.

Practical Example: emplace_back Principle

std::vector::emplace_back receives construction arguments and creates element in-place. Perfect Forwarding passes arguments directly to constructor. Calling v.emplace_back(1, "hello"); directly constructs pair(1, "hello") without copy/move.

Reference: Detailed Perfect Forwarding behavior (reference collapsing, universal reference conditions) covered in C++ Practical Guide #14-2: Perfect Forwarding.


6. Common Mistakes and Cautions

Mistake 1: Adding std::move to return value (Harms RVO)

아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ Bad: loses RVO opportunity
std::vector<int> createBad() {
    std::vector<int> vec(1000);
    return std::move(vec);  // Unnecessary! Harms RVO
}
// ✅ Good: compiler applies RVO or move
std::vector<int> createGood() {
    std::vector<int> vec(1000);
    return vec;  // RVO or move
}

Why is return std::move(vec) bad?
Just return vec allows compiler to directly construct vec in return value location (RVO). Using return std::move(vec) forces “move already-created vec for return”, breaking RVO conditions and forcing one move instead. Don’t add std::move when returning local variables.

Mistake 2: Using original after move

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <string>
#include <iostream>
int main() {
    std::string str = "Hello";
    std::string str2 = std::move(str);
    // ❌ Dangerous: using moved object (usually empty string but not guaranteed)
    std::cout << str << "\n";
    // ✅ Usable after reassignment
    str = "World";
    std::cout << str << "\n";  // "World"
    return 0;
}

After move, original is in “valid but unspecified” state. std::string usually becomes empty string, but standard doesn’t guarantee value, so don’t use after move for safe coding.

Mistake 3: Missing noexcept on move constructor

아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ Bad: vector reallocation uses copy (slow)
class Slow {
    std::vector<int> data;
public:
    Slow(Slow&& other) { data = std::move(other.data); }
};
// ✅ Good: vector reallocation uses move (fast)
class Fast {
    std::vector<int> data;
public:
    Fast(Fast&& other) noexcept { data = std::move(other.data); }
};

std::vector uses move when reallocating elements to new buffer if move constructor is noexcept, otherwise uses copy. Because exceptions during move create “partially moved” state difficult to recover.

Mistake 4: const rvalue reference

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ❌ Bad: const prevents move
void process(const std::string&& str) {
    // Can't move str
}
// ✅ Good
void process(std::string&& str) {
    std::string local = std::move(str);  // Movable
}

Mistake 5: Missing self-assignment check in copy assignment

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <algorithm>
#include <cstddef>
// ❌ Bad: a = a causes delete[] data then access other.data → undefined behavior
class BadAssign {
    int* data;
    size_t size;
public:
    BadAssign& operator=(const BadAssign& other) {
        delete[] data;                    // Frees own memory when a = a!
        data = new int[other.size];       // other.data already freed → UB on access
        size = other.size;
        std::copy(other.data, other.data + size, data);
        return *this;
    }
};
// ✅ Good: self-assignment check
class GoodAssign {
    int* data;
    size_t size;
public:
    GoodAssign& operator=(const GoodAssign& other) {
        if (this != &other) {
            delete[] data;
            data = new int[other.size];
            size = other.size;
            std::copy(other.data, other.data + other.size, data);
        }
        return *this;
    }
};

Mistake 6: Missing self-assignment check in move assignment

다음은 cpp를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ Bad: a = std::move(a) makes other.data nullptr, so own data also nullptr
Resource& operator=(Resource&& other) noexcept {
    delete[] data;           // Free own memory
    data = other.data;        // If other is self, data is already-freed pointer
    other.data = nullptr;     // Make own data nullptr → memory leak
    return *this;
}
// ✅ Good
Resource& operator=(Resource&& other) noexcept {
    if (this != &other) {
        delete[] data;
        data = other.data;
        size = other.size;
        other.data = nullptr;
        other.size = 0;
    }
    return *this;
}

Mistake 7: Overusing std::move (Unnecessary moves)

아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ Bad: moving name still needed
void process(const std::string& name) {
    names.push_back(std::move(name));  // ❌ const reference not movable
}
void greet(std::string name) {
    std::cout << "Hello, " << name << "\n";
    log.push_back(std::move(name));  // ❌ name still used in output
}
// ✅ Good: move only when no longer used
void addName(std::string name) {
    log.push_back(std::move(name));  // name received by value, not used after
}

Mistake 8: Relying on default copy/move

아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ Bad: pointer member but no copy constructor
class RawPtrHolder {
    int* ptr;
public:
    RawPtrHolder() : ptr(new int(42)) {}
    ~RawPtrHolder() { delete ptr; }
    // No copy constructor → compiler default = shallow copy
};
int main() {
    RawPtrHolder a;
    RawPtrHolder b = a;  // b.ptr == a.ptr → double free
    return 0;
}

Solution: Apply Rule of Three/Five implementing copy constructor & assignment (and moves if needed), or manage resources with smart pointers like std::unique_ptr.

Mistake 9: Missing std::forward in Perfect Forwarding

아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ Bad: arg passed only as lvalue
template <typename T>
void wrapper(T&& arg) {
    process(arg);  // arg is named, so lvalue → always copies
}
// ✅ Good
template <typename T>
void wrapper(T&& arg) {
    process(std::forward<T>(arg));  // Maintain lvalue/rvalue property
}

Mistake 10: Copying non-movable type in move constructor

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <mutex>
#include <vector>
// ❌ Bad: mutex not movable but attempting copy
class BadSync {
    std::mutex mtx;
    std::vector<int> data;
public:
    BadSync(BadSync&& other) : data(std::move(other.data)) {
        mtx = std::move(other.mtx);  // ❌ mutex not movable
    }
};
// ✅ Good: non-movable members newly constructed
class GoodSync {
    std::mutex mtx;
    std::vector<int> data;
public:
    GoodSync(GoodSync&& other) noexcept
        : mtx{}, data(std::move(other.data)) {}  // mtx default constructed
};

7. Performance Comparison

Copy vs Move Benchmark

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <chrono>
#include <iostream>
#include <vector>
#include <string>
void testCopy() {
    auto start = std::chrono::high_resolution_clock::now();
    std::vector<std::string> vec1(10000, std::string(1000, 'x'));
    std::vector<std::string> vec2 = vec1;  // Copy
    auto end = std::chrono::high_resolution_clock::now();
    auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
    std::cout << "Copy: " << ms << " ms\n";
}
void testMove() {
    auto start = std::chrono::high_resolution_clock::now();
    std::vector<std::string> vec1(10000, std::string(1000, 'x'));
    std::vector<std::string> vec2 = std::move(vec1);  // Move
    auto end = std::chrono::high_resolution_clock::now();
    auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
    std::cout << "Move: " << ms << " ms\n";
}
int main() {
    testCopy();  // Dozens to hundreds of ms depending on environment
    testMove();  // Usually < 1ms
    return 0;
}

Expected results (varies by environment):

  • Copy: Dozens to hundreds of ms (10,000 strings × 1000 chars = ~10MB copy)
  • Move: < 1ms (only pointer exchange) Move having much lower cost than copy is key. Using moves when transferring large containers or resources significantly improves perceived performance.

Summary Table

ItemCopyMove
Memory allocationAllocate newNone
Data duplicationFull duplicationOnly pointer exchange
Time complexityO(n)O(1)
Original stateMaintainedUnspecified (usually empty)

When to copy, when to move?

SituationRecommendedReason
Function return (local variable)return vec; (no std::move)RVO or automatic move
Add to container (object no longer used)push_back(std::move(x))Move instead of copy
swapstd::swap(a, b)Already uses move
unique_ptr transferstd::move(ptr)Not copyable, only movable
Receiving temporary as argumentvoid f(T&& t)rvalue overload for move

8. Best Practices

8.1 Copy vs Move Selection Guide

Refer to “When to copy, when to move?” table in Performance Comparison section above. Key: prohibit std::move on local variable returns, move only objects no longer used.

8.2 Move Constructor/Assignment Writing Checklist

  • Specify noexcept: So std::vector, etc. select move on reallocation
  • Null-initialize original: other.ptr = nullptr, etc. to prevent double-free
  • Self-assignment check: if (this != &other) (needed in move assignment too)
  • Free existing resources: In move assignment, first free with delete, etc.

8.3 Prioritize Smart Pointers

When resource management gets complex, using std::unique_ptr, std::shared_ptr is safer than implementing Rule of Five yourself. Smart pointers already have copy/move semantics defined. 아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ✅ unique_ptr: not copyable, only movable
class SafeHolder {
    std::unique_ptr<int> ptr;
public:
    SafeHolder() : ptr(std::make_unique<int>(42)) {}
    // Copy constructor/assignment = delete (default)
    // Move constructor/assignment = auto-generated
};

8.4 Zero-Rule: Avoid Resource Management

When possible, pursue design not directly managing resources. Using standard library types like std::vector, std::string as members makes copy/move automatically work correctly. 아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ✅ No resource management: vector handles it
class SimpleData {
    std::vector<int> data;
public:
    // All copy/move constructor/assignment compiler-provided
};

8.5 Pass-by-value + move

Receiving argument “by value” and storing with std::move inside causes move if caller passes rvalue, copy if lvalue—only once. No need for two overloads. 아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.

class DataStore {
    std::string name_;
public:
    // ✅ Receive by value: lvalue → 1 copy, rvalue → move
    void setName(std::string name) {
        name_ = std::move(name);
    }
};

8.6 When Copy/Move Prohibition Needed

Resources like file handles, sockets, mutexes where copying is meaningless should explicitly prohibit copying with delete. 아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.

class NonCopyable {
    int* resource;
public:
    NonCopyable(const NonCopyable&) = delete;
    NonCopyable& operator=(const NonCopyable&) = delete;
    NonCopyable(NonCopyable&&) = default;
    NonCopyable& operator=(NonCopyable&&) = default;
};

9. Production Patterns

9.1 Move Return in Factory Functions

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// Common production pattern: factory function
std::unique_ptr<Config> loadConfig(const std::string& path) {
    auto config = std::make_unique<Config>();
    config->parse(path);
    return config;  // Return without std::move (RVO/move)
}

9.2 Utilize emplace_back in Containers

// emplace_back instead of push_back(std::move(x)) for creation and insertion in one step
std::vector<std::pair<int, std::string>> items;
items.emplace_back(1, "hello");  // Direct construction without copy/move

9.3 Optional Return Pattern

아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 코드를 직접 실행해보면서 동작을 확인해보세요.

#include <optional>
std::optional<std::vector<int>> fetchData() {
    std::vector<int> result = /* ....*/;
    return result;  // Moved into optional
}

9.4 Combining RAII and Move

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class FileHandle {
    FILE* f;
public:
    FileHandle(const char* path) : f(fopen(path, "r")) {}
    ~FileHandle() { if (f) fclose(f); }
    FileHandle(FileHandle&& other) noexcept : f(other.f) { other.f = nullptr; }
    FileHandle& operator=(FileHandle&& other) noexcept {
        if (this != &other) {
            if (f) fclose(f);
            f = other.f;
            other.f = nullptr;
        }
        return *this;
    }
    // Prohibit copy
    FileHandle(const FileHandle&) = delete;
    FileHandle& operator=(const FileHandle&) = delete;
};

9.5 Move in Pimpl Idiom

아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.

// Pimpl: hide implementation while supporting move
class Widget {
    struct Impl;
    std::unique_ptr<Impl> pImpl;
public:
    Widget(Widget&&) = default;
    Widget& operator=(Widget&&) = default;
    // unique_ptr only supports move, so Widget also only movable
};

9.6 Perfect Forwarding Factory

In template factories passing constructor arguments as-is, use std::forward. Inside make_unique<T>(args...), passes like new T(std::forward<Args>(args)...).

9.7 Utilizing Move on Vector Reallocation

std::vector reallocates when capacity insufficient on push_back. If element’s move constructor is noexcept, moves elements; otherwise uses copy. Always specify noexcept on custom type move constructors. 아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

class Element {
    std::vector<int> data;
public:
    Element(Element&& other) noexcept  // noexcept essential
        : data(std::move(other.data)) {}
};

10. Interview Q&A Summary

Q: Difference between shallow and deep copy?

Shallow copy copies pointer values only, pointing at same resource; deep copy allocates new resources duplicating contents to create independent copy. Classes directly managing resources must implement deep copy to prevent double-free and dangling pointers.

Q: When must I implement copy constructor and assignment myself?

When class owns resources like heap memory via pointers. Default copy is shallow copy, dangerous, so implement deep copy in copy constructor & assignment (Rule of Three).

Q: Why is move semantics needed?

To reduce unnecessary copy costs. Transferring resources from objects “no longer used” without duplication eliminates allocation and duplication costs, improving performance when returning values or adding to containers.

Q: What does std::move do?

Casts lvalue argument to rvalue, so receiving side selects move constructor/assignment. Expresses intent “OK to transfer this object’s resources”. Doesn’t actually move, only casts.

Q: What about original after move?

Left in valid but unspecified state. Usually no longer used, or only used after reassignment. Standard library types (vector, etc.) behave this way too.

Q: Difference between std::forward and std::move?

  • std::move: Always casts lvalue to rvalue. Use when explicitly stating “no longer using this object”.
  • std::forward: In template wrappers passing arguments to next function, maintains original lvalue/rvalue property. Use with T&&. Organizing this script-like enables answering interviews in flow: “shallow/deep copy → copy construct/assign → why moves needed → rvalue, std::move → Perfect Forwarding”.

30-Second Interview Summary

  1. Shallow vs deep: Shallow copy only copies pointers sharing same resource (dangerous), deep copy allocates new resources creating independent copy.
  2. Rule of Three/Five: Resource-managing classes design destructor, 2 copies, (optional) 2 moves together.
  3. Why moves needed: Eliminate unnecessary copy costs. “Transfer” resources without duplication.
  4. std::move: Casts lvalue to rvalue enabling move operation selection. Original unspecified after move.

11. C++03 vs C++11: Return Value Differences

C++03 lacked move semantics, so returning large objects from functions always caused copying. Old code conventions often said “don’t return large objects, use reference parameters to fill instead.” 아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// C++03 style: out parameter instead of return
// 실행 예제
void createVector(std::vector<int>& out) {
    out.resize(1000000);
    // Fill data...
}
int main() {
    std::vector<int> data;
    createVector(data);  // Fill data directly without copy
    return 0;
}

In C++11, just return vec; causes move, eliminating need for above pattern. Code is more intuitive, and if RVO applies, there might be no copy or move at all.

Self-Verification

  • Create function returning std::vector, compile with both return vec; and return std::move(vec);, then compare constructor call counts output.
  • Create simple class with pointer member, copy without copy constructor, then run. (May abnormally terminate from double-free)
  • Compare name state after push_back(name) vs push_back(std::move(name)) output.


Keywords

C++ copy move, shallow copy deep copy, Rule of Three, Rule of Five, rvalue reference, std::move, perfect forwarding, interview

Summary

  • Shallow copy: Copy pointers only → share same resource → danger. Deep copy: Allocate new resources & duplicate → independent copy.
  • Classes directly managing resources implement copy constructor & assignment (and destructor, 2 moves if needed) (Rule of Three/Five).
  • Move semantics: Transfer resources from objects “no longer used” without copying to reduce costs. Express with rvalue references and std::move.
  • std::move: Casts lvalue to rvalue enabling move operation selection. Leave original in unspecified state after move.
  • Cautions: Don’t add std::move to return values (harms RVO), prohibit using original after move, specify noexcept on move constructors.

Reference: This article compressed for interview script format. Detailed move semantics behavior, RVO/NRVO, perfect forwarding covered in C++ Practical Guide #14-1: Move Semantics and #14-2: Perfect Forwarding.

Frequently Asked Questions (FAQ)

Q. When do I use this in production?

A. Copy constructor, rvalue reference, std::move organized in interview script format. In production, apply by referring to Best Practices and Production Patterns in main text above. Especially using moves in std::vector::push_back(std::move(x)), function return values, swap implementation, factory functions, emplace_back, etc. improves performance.

Q. What should I read first?

A. Follow Previous links at bottom of each article to learn in order. Check C++ Series Index for complete flow. Also read C++ Practical Guide #14-1: Move Semantics for deeper move semantics coverage.

Q. How to study deeper?

A. Refer to cppreference Move semantics, std::move documentation. C++ Practical Guide #14-1: Move Semantics covers deeper topics like RVO, perfect forwarding. One-line summary: Distinguishing shallow/deep copy and move semantics clarifies resource management and interview answers. Next, read Smart Pointers & Circular References (#33-3). Next: [C++ Interview #33-3] Smart Pointers & Circular Reference Solutions Previous: [C++ Interview #33-1] Virtual Functions & vtable Mechanics

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3