OOP Bài 11: Destructor (Hàm hủy) và Cơ chế giải phóng tài nguyên
1. Bản chất của Destructor
Destructor là một phương thức đặc biệt được tự động kích hoạt ngay trước khi Object bị thu hồi khỏi bộ nhớ Heap.
Nhiệm vụ duy nhất của nó: Dọn dẹp "bãi chiến trường" mà Object đã bày ra trong suốt vòng đời của mình. Những tài nguyên này thường không nằm trong bản thân Object, mà là các tài nguyên của hệ điều hành:
- Đóng kết nối tới Database (MySQL, PostgreSQL).
- Nhả khóa (Release Lock) các file log hoặc file hệ thống.
- Ngắt kết nối mạng (Network Sockets).
- Giải phóng vùng nhớ con trỏ cấp phát động (đối với C++).
2. Thế giới thủ công của C++: Bố đời làm chủ sinh mệnh
Trong C++, bạn là vị thần tối cao. OS cấp RAM cho bạn thông qua new, nhưng OS không bao giờ tự động thu hồi nó. Bạn phải tự gọi delete. Khi gọi delete, Destructor (có dấu ngã ~ phía trước) sẽ chạy.
Nếu bạn quên viết Destructor hoặc quên gọi delete trong một hệ thống chạy liên tục như phần mềm nhúng trên máy Ticket Vending Machine (TVM), RAM sẽ bị rỉ rả ăn mòn cho đến khi màn hình xanh (Out of Memory).
class TVMHardwareController {
private:
int* buffer;
FILE* logFile;
public:
// Constructor: Dành lấy tài nguyên
TVMHardwareController() {
buffer = new int[1024]; // Cấp phát động
logFile = fopen("tvm_hardware.log", "a"); // Mở file log
cout << "Đã kết nối phần cứng TVM.\n";
}
// Destructor: Trả lại tài nguyên cho Hệ điều hành
~TVMHardwareController() {
delete[] buffer; // Trả lại RAM
fclose(logFile); // Nhả quyền kiểm soát file
cout << "Đã ngắt kết nối phần cứng an toàn.\n";
}
};
void processCard() {
TVMHardwareController controller; // Object sinh ra trên Stack
// Xử lý logic...
} // Hết scope hàm processCard, Destructor của controller tự động chạy!
3. Thế giới tự động của PHP, Java (Garbage Collector)
Ở các ngôn ngữ bậc cao như PHP, hệ thống có một công nhân vệ sinh chạy ngầm gọi là Garbage Collector (GC).
GC hoạt động dựa trên cơ chế Reference Counting (Đếm tham chiếu). Mỗi khi một biến trỏ tới Object, bộ đếm +1. Khi biến đó bị hủy (ví dụ: hàm chạy xong) hoặc bị gán bằng null, bộ đếm -1. Khi bộ đếm về 0, GC nhận ra không còn ai quan tâm đến Object này nữa. Nó sẽ tự động gọi hàm __destruct() rồi xóa Object khỏi Heap. Bạn không cần (và không thể) gọi Destructor bằng tay.
class DatabaseConnection {
private $pdo;
public function __construct() {
$this->pdo = new PDO('mysql:host=localhost;dbname=afc', 'root', '');
echo "DB Connected!\n";
}
public function __destruct() {
// Chủ động ngắt kết nối thay vì đợi script PHP tự timeout
$this->pdo = null;
echo "DB Disconnected. Trả connection về Pool.\n";
}
}
function runQuery() {
$db = new DatabaseConnection(); // Ref count = 1
// Làm gì đó...
} // Hàm kết thúc, $db bị hủy -> Ref count = 0 -> __destruct() tự chạy!
Lưu ý ở PHP: Với các script chạy một lần rồi tắt (FPM request), PHP sẽ tự dọn dẹp mọi thứ khi request kết thúc. Nhưng nếu bạn viết các worker chạy command line (như Laravel Queue), việc thiết kế
__destruct()để nhả kết nối DB là bắt buộc để tránh lỗi "Too many connections".
4. Triết lý của Go (Golang): Không có Destructor!
Go được thiết kế để giải quyết các vấn đề đồng thời (Concurrency) ở scale lớn. Cha đẻ của Go quyết định vứt bỏ khái niệm Destructor của OOP truyền thống vì nó làm luồng chạy khó đoán (bạn không biết chính xác lúc nào GC sẽ kích hoạt Destructor).
Thay vào đó, Go ép lập trình viên quản lý tài nguyên một cách tường minh (explicit) ngay tại nơi khởi tạo bằng từ khóa defer.
func processTransaction() error {
// Mở kết nối
db, err := sql.Open("mysql", "user:pass@/afc")
if err != nil {
return err
}
// Ghi chú ngay lập tức: "Lát nữa hàm này chạy xong nhớ đóng DB nhé"
// defer đóng vai trò y hệt một Destructor cục bộ, nhưng dễ kiểm soát hơn rất nhiều
defer db.Close()
// Thực thi các logic truy vấn...
return nil
} // db.Close() đảm bảo 100% sẽ được thực thi tại đây
💡 Tổng kết
Destructor là chốt chặn cuối cùng bảo vệ tính ổn định của hệ thống. Hiểu rõ nó giúp bạn tránh được 90% các lỗi "rỉ máu" tài nguyên server khi deploy hệ thống ra môi trường production thật.
All rights reserved