[2026] C++ Copy & Move Constructors: Rule of Five, RAII, and noexcept

[2026] C++ Copy & Move Constructors: Rule of Five, RAII, and noexcept

이 글의 핵심

Rule of Five in C++: copy/move constructors and assignment, deep copy vs shallow, self-assignment, noexcept moves, copy elision, and FileHandle patterns.

Rule of Five

Types that manage resources should define the five special members (constructor, destructor, copy ctor, copy assign, move ctor, move assign—often counted as five “operations” plus destructor). 다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class Resource {
private:
    int* data;
    size_t size;
    
public:
    Resource(size_t s) : size(s), data(new int[s]) {}
    
    ~Resource() {
        delete[] data;
    }
    
    Resource(const Resource& other) 
        : size(other.size), data(new int[other.size]) {
        copy(other.data, other.data + size, data);
    }
    
    Resource& operator=(const Resource& other) {
        if (this != &other) {
            delete[] data;
            size = other.size;
            data = new int[size];
            copy(other.data, other.data + size, data);
        }
        return *this;
    }
    
    Resource(Resource&& other) noexcept
        : size(other.size), data(other.data) {
        other.data = nullptr;
        other.size = 0;
    }
    
    Resource& operator=(Resource&& other) noexcept {
        if (this != &other) {
            delete[] data;
            data = other.data;
            size = other.size;
            other.data = nullptr;
            other.size = 0;
        }
        return *this;
    }
};

Copy constructor

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

class String {
private:
    char* data;
    size_t length;
    
public:
    String(const char* str) {
        length = strlen(str);
        data = new char[length + 1];
        strcpy(data, str);
    }
    
    String(const String& other) {
        length = other.length;
        data = new char[length + 1];
        strcpy(data, other.data);
        cout << "copy ctor" << endl;
    }
    
    ~String() {
        delete[] data;
    }
};
int main() {
    String s1("Hello");
    String s2 = s1;  // copy ctor
    String s3(s1);   // copy ctor
}

Move constructor

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

class String {
private:
    char* data;
    size_t length;
    
public:
    String(const char* str) {
        length = strlen(str);
        data = new char[length + 1];
        strcpy(data, str);
    }
    
    String(String&& other) noexcept {
        data = other.data;
        length = other.length;
        
        other.data = nullptr;
        other.length = 0;
        
        cout << "move ctor" << endl;
    }
    
    ~String() {
        delete[] data;
    }
};
int main() {
    String s1("Hello");
    String s2 = move(s1);  // move ctor
}

Practical examples

Example 1: Dynamic array

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

template<typename T>
class DynamicArray {
private:
    T* data;
    size_t size;
    size_t capacity;
    
public:
    DynamicArray(size_t cap = 10) 
        : size(0), capacity(cap), data(new T[cap]) {}
    
    ~DynamicArray() {
        delete[] data;
    }
    
    DynamicArray(const DynamicArray& other)
        : size(other.size), capacity(other.capacity), 
          data(new T[other.capacity]) {
        copy(other.data, other.data + size, data);
    }
    
    DynamicArray& operator=(const DynamicArray& other) {
        if (this != &other) {
            delete[] data;
            size = other.size;
            capacity = other.capacity;
            data = new T[capacity];
            copy(other.data, other.data + size, data);
        }
        return *this;
    }
    
    DynamicArray(DynamicArray&& other) noexcept
        : size(other.size), capacity(other.capacity), data(other.data) {
        other.data = nullptr;
        other.size = 0;
        other.capacity = 0;
    }
    
    DynamicArray& operator=(DynamicArray&& other) noexcept {
        if (this != &other) {
            delete[] data;
            data = other.data;
            size = other.size;
            capacity = other.capacity;
            other.data = nullptr;
            other.size = 0;
            other.capacity = 0;
        }
        return *this;
    }
    
    void push_back(const T& value) {
        if (size == capacity) {
            resize();
        }
        data[size++] = value;
    }
    
    T& operator {
        return data[index];
    }
    
    size_t getSize() const {
        return size;
    }
    
private:
    void resize() {
        capacity *= 2;
        T* newData = new T[capacity];
        copy(data, data + size, newData);
        delete[] data;
        data = newData;
    }
};
int main() {
    DynamicArray<int> arr1;
    arr1.push_back(1);
    arr1.push_back(2);
    
    DynamicArray<int> arr2 = arr1;      // copy
    DynamicArray<int> arr3 = move(arr1); // move
}

Example 2: File handle

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

class FileHandle {
private:
    FILE* file;
    string filename;
    
public:
    FileHandle(const string& name) : filename(name) {
        file = fopen(name.c_str(), "r");
        if (!file) {
            throw runtime_error("failed to open file");
        }
    }
    
    ~FileHandle() {
        if (file) {
            fclose(file);
        }
    }
    
    FileHandle(const FileHandle&) = delete;
    FileHandle& operator=(const FileHandle&) = delete;
    
    FileHandle(FileHandle&& other) noexcept
        : file(other.file), filename(move(other.filename)) {
        other.file = nullptr;
    }
    
    FileHandle& operator=(FileHandle&& other) noexcept {
        if (this != &other) {
            if (file) {
                fclose(file);
            }
            file = other.file;
            filename = move(other.filename);
            other.file = nullptr;
        }
        return *this;
    }
    
    string read() {
        if (!file) return "";
        
        fseek(file, 0, SEEK_END);
        long size = ftell(file);
        fseek(file, 0, SEEK_SET);
        
        string content(size, '\0');
        fread(&content[0], 1, size, file);
        
        return content;
    }
};
int main() {
    FileHandle fh1("test.txt");
    // FileHandle fh2 = fh1;  // error: copy deleted
    FileHandle fh2 = move(fh1);  // OK
}

Example 3: Tiny unique_ptr-like type

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

template<typename T>
class UniquePtr {
private:
    T* ptr;
    
public:
    explicit UniquePtr(T* p = nullptr) : ptr(p) {}
    
    ~UniquePtr() {
        delete ptr;
    }
    
    UniquePtr(const UniquePtr&) = delete;
    UniquePtr& operator=(const UniquePtr&) = delete;
    
    UniquePtr(UniquePtr&& other) noexcept : ptr(other.ptr) {
        other.ptr = nullptr;
    }
    
    UniquePtr& operator=(UniquePtr&& other) noexcept {
        if (this != &other) {
            delete ptr;
            ptr = other.ptr;
            other.ptr = nullptr;
        }
        return *this;
    }
    
    T& operator*() const {
        return *ptr;
    }
    
    T* operator->() const {
        return ptr;
    }
    
    T* get() const {
        return ptr;
    }
    
    T* release() {
        T* temp = ptr;
        ptr = nullptr;
        return temp;
    }
};
int main() {
    UniquePtr<int> p1(new int(42));
    // UniquePtr<int> p2 = p1;  // error
    UniquePtr<int> p2 = move(p1);  // OK
    
    cout << *p2 << endl;  // 42
}

Copy elision

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

String createString() {
    return String("Hello");  // may elide copy/move
}
int main() {
    String s = createString();  // often no copy/move (C++17 guaranteed in many cases)
}

Common pitfalls

Pitfall 1: Shallow copy

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

// Bad: shallow copy (default copy ctor)
class BadString {
private:
    char* data;
    
public:
    BadString(const char* str) {
        data = new char[strlen(str) + 1];
        strcpy(data, str);
    }
    
    ~BadString() {
        delete[] data;
    }
};
BadString s1("Hello");
BadString s2 = s1;  // same pointer
// double free on destruction
// Good: deep copy
class GoodString {
private:
    char* data;
    
public:
    GoodString(const char* str) {
        data = new char[strlen(str) + 1];
        strcpy(data, str);
    }
    
    GoodString(const GoodString& other) {
        data = new char[strlen(other.data) + 1];
        strcpy(data, other.data);
    }
    
    ~GoodString() {
        delete[] data;
    }
};

Pitfall 2: Self-assignment

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

// Bad: no self-check
Resource& operator=(const Resource& other) {
    delete[] data;  // breaks on self-assign
    size = other.size;
    data = new int[size];
    copy(other.data, other.data + size, data);
    return *this;
}
// Good
Resource& operator=(const Resource& other) {
    if (this != &other) {
        delete[] data;
        size = other.size;
        data = new int[size];
        copy(other.data, other.data + size, data);
    }
    return *this;
}

Pitfall 3: Missing noexcept

아래 코드는 cpp를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Without noexcept
Resource(Resource&& other) {
    // ...
}
// With noexcept
Resource(Resource&& other) noexcept {
    // ...
}
// Reason: std::vector prefers noexcept moves when reallocating

FAQ

Q1: When is Rule of Five needed?

A: When you directly manage resources (memory, handles, etc.).

Q2: Copy vs move?

A:

  • Copy: independent duplicate
  • Move: transfer ownership—usually faster

Q3: = delete?

A: To forbid copy/move—like unique_ptr.

Q4: Why noexcept?

A: Exception safety and container optimizations.

Q5: Rule of Zero?

A: Use smart pointers and RAII wrappers—often no manual special members.

Q6: Learning resources?

A:

  • Effective Modern C++
  • cppreference.com
  • C++ Primer

Practical tips

Debugging

  • Warnings first

Performance

  • Profile

Code review

  • Conventions

Practical checklist

Before coding

  • Right approach?
  • Maintainable?
  • Performance?

While coding

  • Warnings?
  • Edge cases?
  • Errors?

At review

  • Intent?
  • Tests?
  • Docs?

Keywords

C++, copy constructor, move constructor, RAII, Rule of Five

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