Copy Constructor là constructor đặc biệt khởi tạo đối tượng mới từ đối tượng cùng loại đã tồn tại. Dấu hiệu nhận biết: tham số là lvalue reference const của cùng lớp.
Cú pháp chuẩn
ClassName(const ClassName& other); // const lvalue reference // Hoặc (hiếm): ClassName(ClassName& other); // non-const – không bind được temporary
3 trường hợp GỌI COPY CONSTRUCTOR
- Khởi tạo đối tượng từ đối tượng khác:
Line line1(10); Line line2 = line1; // copy init -> gọi copy ctor Line line3(line1); // direct init -> gọi copy ctor
- Truyền đối tượng theo giá trị (by value) vào hàm:
void display(Line obj); // tham số by value display(line1); // copy line1 -> obj
- Trả về đối tượng theo giá trị từ hàm (trước C++17, hoặc khi NRVO không áp dụng):
Line createLine() { Line l(20); return l; // có thể gọi copy ctor (hoặc move/elide) }
Ví dụ đầy đủ – theo dõi lời gọi
#include <iostream>
using namespace std;
class Line {
int *ptr;
public:
Line(int len) { cout << "Normal ctor\n"; ptr = new int(len); }
Line(const Line& obj) { cout << "Copy ctor\n"; ptr = new int(*obj.ptr); }
~Line() { cout << "Dtor\n"; delete ptr; }
int getLength() const { return *ptr; }
};
void display(Line obj) { // by value
cout << "Length: " << obj.getLength() << endl;
}
int main() {
Line line1(10); // Normal ctor
display(line1); // Copy ctor (by value)
Line line2 = line1; // Copy ctor (copy init)
display(line2); // Copy ctor
return 0;
}
Kết quả
Normal ctor Copy ctor Length: 10 Copy ctor Copy ctor Length: 10 Dtor Dtor Dtor Dtor
Shallow Copy vs Deep Copy
- Shallow copy (default compiler-generated): copy bitwise các thành viên – con trỏ trỏ cùng địa chỉ → double free, use-after-free.
- Deep copy (tự viết): cấp phát mới, copy dữ liệu trỏ đến – như ví dụ trên.
- Rule of 3/5/0: Nếu cần custom destructor/copy/move → cần đủ 3/5. C++11: thêm move constructor/assignment. C++20: = default.
Copy Elision & RVO/NRVO (C++17)
- Trình biên dịch có quyền bỏ qua copy/move khi trả về local object hoặc khởi tạo từ temporary.
- C++17: Guaranteed copy elision cho prvalue initialization (
Line l = Line(10);– không gọi copy/move). - Vẫn phải khai báo copy/move constructor accessible (public) ngay cả khi elided.
Best Practice
- Nếu class quản lý tài nguyên (con trỏ, handle, socket...) → tự viết deep copy (hoặc delete copy, chỉ cho move).
- Dùng
std::unique_ptr,std::shared_ptr,std::vector,std::string– chúng đã lo copy đúng. - Mặc định:
= defaulthoặc không khai báo (compiler-generated) cho class POD-like. - Truyền đối tượng lớn: const reference (
void func(const Line&)) tránh copy.