Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> C++

Copy Constructor ใน C++: การสร้าง Deep Copy และ Rule of Three/Five

Copy Constructor เป็น Constructor พิเศษที่สร้าง Object ใหม่โดยคัดลอกจาก Object เดียวกัน Class ที่มีอยู่แล้ว Signature มาตรฐาน:

ClassName(const ClassName& other);

เมื่อไหร่ถูกเรียก?

  1. Initialize Object จาก Object อื่น: ClassName obj2 = obj1;
  2. Pass Object โดย Value ให้ Function: void func(ClassName obj);
  3. Return Object โดย Value จาก Function: ClassName func() { return obj; }
  4. Brace Initialization: ClassName obj2{obj1};

Default Copy Constructor (Compiler-Generated)

ถ้าไม่เขียนเอง Compiler จะสร้าง Member-wise Copy (Shallow Copy) คัดลอกทุก Member ตามลำดับ

  • ✅ ใช้ได้: POD Types (int, double, etc.), std::string, std::vector (มี Copy Constructor ของตัวเอง)
  • ❌ อันตราย: Raw Pointers, Dynamic Memory, Resource Handles (File, Socket, Mutex)

ตัวอย่างปัญหา Shallow Copy (Double Free)

#include <iostream>

class Line {
    int* ptr; // Raw Pointer -> ต้อง Manage เอง
public:
    Line(int len) : ptr(new int(len)) { 
        std::cout << "Constructor\n"; 
    }
    
    // ไม่มี Copy Constructor -> Compiler ให้ Shallow Copy (Copy Pointer Address)
    // Line(const Line& other) : ptr(other.ptr) { } // Implicit
    
    ~Line() { 
        std::cout << "Destructor: delete ptr\n"; 
        delete ptr; 
    }
    
    int getLength() const { return *ptr; }
};

void display(Line obj) { // Pass by Value -> Copy Constructor เรียก
    std::cout << "Length: " << obj.getLength() << '\n';
} // obj ทำลาย -> delete ptr

int main() {
    Line line(10);
    display(line); // Copy -> Shallow Copy
    // line.ptr ถูก Delete แล้ว!
    // line.getLength() -> Use After Free (UB)
    // main จบ -> line ทำลายอีกครั้ง -> Double Free (Crash)
}

แก้ไขด้วย Deep Copy Constructor


class Line {
    int* ptr;
public:
    Line(int len) : ptr(new int(len)) {}
    
    // Deep Copy Constructor
    Line(const Line& other) : ptr(new int(*other.ptr)) {
        std::cout << "Deep Copy Constructor\n";
    }
    
    // Copy Assignment Operator (Rule of Three)
    Line& operator=(const Line& other) {
        if (this != &other) { // Self-assignment Check
            delete ptr;
            ptr = new int(*other.ptr);
        }
        return *this;
    }
    
    ~Line() { delete ptr; }
    
    int getLength() const { return *ptr; }
};

Rule of Three / Five / Zero

RuleFunctionsเมื่อไหร่ใช้
Rule of Three (C++98)Destructor, Copy Ctor, Copy AssignmentManage Resource แบบ Manual (Raw Pointer)
Rule of Five (C++11)+ Move Ctor, Move Assignmentรองรับ Move Semantics
Rule of Zero (Modern)ไม่เขียนอะไรเลยใช้ RAII Types (unique_ptr, vector, string) จัดการ Resource ให้เอง

Modern C++: Rule of Zero (แนะนำ)


#include <memory>
#include <vector>

class ModernLine {
    std::unique_ptr<int> ptr; // หรือ std::vector ptr;
public:
    ModernLine(int len) : ptr(std::make_unique<int>(len)) {}
    // ไม่ต้องเขียน Destructor, Copy, Move - Compiler สร้างให้ถูกต้อง!
    // unique_ptr: Non-copyable, Movable (Transfer Ownership)
    // vector: Deep Copy โดยอัตโนมัติ
};

สรุป

Copy Constructor จำเป็นเมื่อ Class จัดการ Resource เอง (Raw Pointer) ให้เขียน Deep Copy พร้อม Copy Assignment และ Destructor (Rule of Three) แต่ใน Modern C++ ควรใช้ Rule of Zero: ใช้ RAII Containers/Smart Pointers ให้ Compiler Generate Special Member Functions ให้โดยอัตโนมัติ