[C++ OOP Thực Chiến] Bài 16: Copy Constructor - Nghệ thuật nhân bản và cạm bẫy "Double Free"
Chào anh em! Trong C++, khi bạn viết Object A = B;, trình biên dịch sẽ tự động thực hiện một thao tác gọi là Shallow Copy (Sao chép nông). Nó sẽ copy từng byte dữ liệu từ B sang A.
Mọi thứ vẫn ổn cho đến khi Class của bạn có sử dụng con trỏ hoặc cấp phát bộ nhớ động (new). Lúc này, Shallow Copy sẽ khiến cả A và B cùng trỏ vào một vùng nhớ duy nhất. Hãy tưởng tượng A và B là hai người nhưng lại dùng chung một chiếc ví tiền. Một người tiêu tiền, người kia cũng mất tiền. Và khi một người "biến mất" và vứt chiếc ví đi, người còn lại sẽ cầm một cái ví rỗng (vùng nhớ đã bị giải phóng) – đây chính là thảm họa!
1. Copy Constructor là gì?
Copy Constructor là một hàm khởi tạo đặc biệt, dùng để tạo ra một Object mới dựa trên một Object đã tồn tại.
Cú pháp chuẩn của một Senior C++:
TênClass(const TênClass& other);
- Dùng Tham chiếu (
&): Để tránh việc C++ lại tiếp tục gọi copy constructor một cách vô tận (đệ quy). - Dùng
const: Để đảm bảo bạn không lỡ tay làm thay đổi dữ liệu của Object gốc khi đang copy.
2. Shallow Copy vs. Deep Copy (Sao chép nông và sâu)
- Shallow Copy (Mặc định): Chỉ copy giá trị của các biến. Nếu là con trỏ, nó chỉ copy địa chỉ vùng nhớ. Kết quả: 2 Object dùng chung 1 vùng nhớ.
- Deep Copy (Yêu cầu thực chiến): Bạn tự tay xin cấp phát một vùng nhớ mới cho Object bản sao, sau đó copy toàn bộ nội dung từ vùng nhớ gốc sang. Kết quả: 2 Object hoàn toàn độc lập.
3. Code Demo: Xây dựng Class SmartBuffer an toàn
Hãy xem cách chúng ta triển khai Deep Copy để bảo vệ dữ liệu hệ thống:
#include <iostream>
#include <cstring>
using namespace std;
class SmartBuffer {
private:
int* data;
int size;
public:
// Constructor thông thường
SmartBuffer(int s) : size(s) {
data = new int[size];
for (int i = 0; i < size; i++) data[i] = i * 10;
cout << "[LOG] Da cap phat vung nho tai: " << data << "\n";
}
// COPY CONSTRUCTOR (Triển khai DEEP COPY)
SmartBuffer(const SmartBuffer& other) {
this->size = other.size;
// 1. Cap phat vung nho moi hoan toan
this->data = new int[this->size];
// 2. Copy noi dung tu vung nho cua Object 'other' sang vung nho moi
for (int i = 0; i < size; i++) {
this->data[i] = other.data[i];
}
cout << "[LOG] DEEP COPY thanh cong vao vung nho moi: " << this->data << "\n";
}
void updateValue(int index, int newValue) {
if (index >= 0 && index < size) data[index] = newValue;
}
void print() const {
cout << "Data: ";
for (int i = 0; i < size; i++) cout << data[i] << " ";
cout << " (Address: " << data << ")\n";
}
// Luu y: Ham huy se duoc hoc o bai sau de giai phong 'data'
};
int main() {
cout << "--- KHOI TAO BAN GOC ---\n";
SmartBuffer original(3);
original.print();
cout << "\n--- TAO BAN SAO (Dung Copy Constructor) ---\n";
SmartBuffer clone = original; // Hoac SmartBuffer clone(original);
clone.print();
cout << "\n--- THAY DOI BAN SAO ---\n";
clone.updateValue(0, 999);
cout << "Original: "; original.print();
cout << "Clone: "; clone.print();
return 0;
}
Nhận xét: Nhìn vào địa chỉ vùng nhớ (Address) in ra, bạn sẽ thấy hai địa chỉ khác nhau hoàn toàn. Khi bạn sửa giá trị ở clone, dữ liệu ở original vẫn được giữ nguyên. Đây chính là cách làm của một chuyên gia!
Tạm kết & Gợi mở
Copy Constructor giúp chúng ta kiểm soát quá trình nhân bản đối tượng, đảm bảo an toàn cho bộ nhớ động. Đây là kỹ năng bắt buộc khi làm các hệ thống Backend đòi hỏi sự chính xác cao.
Nhưng có một câu hỏi lớn: Chúng ta dùng new để xin cấp phát bộ nhớ, vậy khi Object không còn dùng nữa, ai sẽ là người đi dọn dẹp đống rác đó? Nếu chúng ta quên không trả lại RAM cho hệ điều hành, server sẽ bị lỗi Memory Leak và sập chỉ sau vài giờ hoạt động.
Trong C++, có một "người hùng thầm lặng" sẽ tự động xuất hiện ngay trước khi một Object "qua đời" để làm nhiệm vụ dọn dẹp cuối cùng.
Hẹn gặp lại các bạn ở Bài 17: Hàm phá hủy (Destructor) - Người dọn rác tận tụy của C++. Đừng quên Upvote để ủng hộ mình nhé!
All rights reserved