Sao chép nông vs Sao chép sâu
Vấn đề
Phần tiêu đề “Vấn đề”Khi một struct/class chứa con trỏ, phép gán mặc định (=) chỉ sao chép địa chỉ mà con trỏ đang giữ, không sao chép dữ liệu mà nó trỏ đến - đây gọi là sao chép nông (shallow copy):
#include <iostream>
struct Buffer { int* data;
Buffer(int value) { data = new int(value); }};
int main() { Buffer a(10); Buffer b = a; // Sao chép NÔNG - b.data trỏ đến CÙNG địa chỉ với a.data!
*b.data = 100; // Thay đổi thông qua b... std::cout << *a.data << std::endl; // 100 (!) - a cũng bị ảnh hưởng, dù không ai đụng vào a
return 0;}Nguy hiểm hơn: double free khi cả hai bị hủy
Phần tiêu đề “Nguy hiểm hơn: double free khi cả hai bị hủy”struct Buffer { int* data; Buffer(int value) { data = new int(value); } ~Buffer() { delete data; } // Destructor tự động giải phóng};
int main() { Buffer a(10); Buffer b = a; // Sao chép nông - cả a.data và b.data trỏ CÙNG một địa chỉ
return 0;} // Khi a và b bị hủy: delete data ĐƯỢC GỌI HAI LẦN trên CÙNG địa chỉ -> double free, crash!Giải pháp: sao chép sâu (Deep Copy)
Phần tiêu đề “Giải pháp: sao chép sâu (Deep Copy)”Sao chép sâu tạo ra một vùng nhớ mới, hoàn toàn độc lập, chứa cùng giá trị - thường được thực hiện bằng cách tự định nghĩa “copy constructor”:
#include <iostream>
struct Buffer { int* data;
Buffer(int value) { data = new int(value); }
// Copy constructor tùy chỉnh - thực hiện sao chép SÂU Buffer(const Buffer& other) { data = new int(*other.data); // Cấp phát vùng nhớ MỚI, sao chép GIÁ TRỊ }
~Buffer() { delete data; }};
int main() { Buffer a(10); Buffer b = a; // Giờ gọi copy constructor tùy chỉnh -> sao chép sâu
*b.data = 100; std::cout << *a.data << std::endl; // 10 (không đổi - a và b hoàn toàn độc lập)
return 0;}Cách hiện đại: dùng smart pointer thay vì con trỏ thô
Phần tiêu đề “Cách hiện đại: dùng smart pointer thay vì con trỏ thô”Trong C++ hiện đại, cách tốt nhất để tránh toàn bộ lớp lỗi này là dùng std::unique_ptr/std::shared_ptr thay vì con trỏ thô - unique_ptr thậm chí còn chặn hoàn toàn việc sao chép ngầm định, buộc bạn phải xử lý tường minh.
Tóm tắt
Phần tiêu đề “Tóm tắt”Sao chép nông: chỉ sao chép địa chỉ con trỏ, khiến hai đối tượng “chia sẻ” cùng vùng nhớ một cách ngoài ý muốn. Sao chép sâu: tạo vùng nhớ mới, hai đối tượng hoàn toàn độc lập.
- Struct/class chứa con trỏ thô cần tự định nghĩa copy constructor để thực hiện sao chép sâu
- Dùng smart pointer hoặc
std::vector/std::stringđể tránh lớp lỗi này gần như hoàn toàn