Những bài trước chúng ta đã có chút kiến thức nhất định về constructor và bây giờ chúng ta sẽ học về destructor.
Destructor cũng là một hàm đặc biệt trong class giống như constructor, và nó sẽ thực thi khi object bị phá hủy (destroy). Constructor được thiết kế để khởi tạo một class, còn destructor được thiết kế để “dọn dẹp” class (giải phóng bộ nhớ).
Khi một object đi ra khỏi phạm vi (out of scope), hay một object được cấp phát động bị xóa bởi từ khóa delete, thì destructor của class được gọi (nếu tồn tại) để dọn dẹp bất cứ thứ gì cần thiết trước khi object bị remove khỏi bộ nhớ. Đối với các class đơn giản, nơi mà chỉ chứa các biến member bình thường, thì không cần destructor vì C++ sẽ tự động dọn dẹp bộ nhớ cho bạn.
Tuy nhiên, nếu trong class của bạn có chứa những thành phần sau: bộ nhớ động (thường chúng ta hay cấp phát bằng từ khóa new), một đối tượng FILE hay một database handle, thì chúng ta cần có một destructor, và destructor là hàm cuối cùng thực thi trước khi object bị phá hủy.
Destructor naming
Giống như constructor, tên của destructor có một số luật như sau:
- Destructor phải có tên giống class, và phải có dấu”~” đứng trước.
- Destructor không được phép có tham số truyền vào (constructor thì vô tư).
- Destructor không có kiểu trả về.
Luật thứ 2 ngụ ý rằng, trong một class chỉ có duy nhất một destructor, và không có chuyện overload ở đây.
Giống như constructor, destructor không nên được gọi một cách rõ ràng tường mình. Tuy nhiên, các destructor có thể gọi các function khác một cách an toàn vì object không bị phá hủy cho đến khi destructor thực thi.
A destructor example
Chúng ta cùng xem một ví dụ
#include <iostream> #include <cassert> class IntArray { private: int *m_array; int m_length; public: IntArray(int length) // constructor { assert(length > 0); m_array = new int[length]; m_length = length; } ~IntArray() // destructor { // Dynamically delete the array we allocated earlier delete[] m_array ; } void setValue(int index, int value) { m_array[index] = value; } int& getValue(int index) { return m_array[index]; } int getLength() { return m_length; } }; int main() { IntArray ar(10); // allocate 10 integers for (int count=0; count < 10; ++count) ar.setValue(count, count+1); std::cout << "The value of element 5 is: " << ar.getValue(5); return 0; } // ar is destroyed here, // so the ~IntArray() destructor function is called here
Kết quả thực hiện chương trình:
The value of element 5 is: 6
Trong dòng đâu tiên của hàm main, chúng ta thiết lập một mảng tên ar với 10 phần tử kiểu int. Đây là cách thiết lập mảng bằng construtor, nhìn vào phần thân của constructor, nơi thực hiện cấp phát động cho các phần tử mảng. Chúng ta phải thực hiện cấp phát động ở đây, vì khi chương trình biên dịch tới đoạn constructor, chúng ta không biết mảng cần cấp phát bao nhiêu phần tử cả.
Tại phần cuối của hàm main, ar đi ra khỏi phạm vi của nó (out of scope). Và khi kết thúc hàm main, destructor được gọi, và đương nhiên, bộ nhớ đã được cấp phát ban đầu cho mảng được giải phóng. Trong ngôn ngữ C (hay C++ hướng cấu trúc) không có chuyện này, cho nên chúng ta phải giải phóng bộ nhớ bằng lệnh delete (trong C++) hoặc free (trong C).
Constructor and destructor timing
Như đã đề cập, constructor được gọi khi một object được tạo, và destructor được gọi khi một object bị phá hủy. Ví dụ bên dưới, chúng ta sử dụng lệnh cout bên trong constructor và destructor để biết khi nào chúng được gọi (đây là một phương pháp debug).
class Simple { private: int m_nID; public: Simple(int nID) { std::cout << "Constructing Simple " << nID << '\n'; m_nID = nID; } ~Simple() { std::cout << "Destructing Simple" << m_nID << '\n'; } int getID() { return m_nID; } }; int main() { // Allocate a Simple on the stack Simple simple(1); std::cout << simple.getID() << '\n'; // Allocate a Simple dynamically Simple *pSimple = new Simple(2); std::cout << pSimple->getID() << '\n'; delete pSimple; return 0; } // simple goes out of scope here
Kết quả thực hiện chương trình trên:
Constructing Simple 1
1
Constructing Simple 2
2
Destructing Simple 2
Destructing Simple 1
Chú ý rằng “Simple 1” bị phá hủy (giải phóng) sau “Simple 2” bởi vì chúng ta delete pSimple trước khi hàm main kết thúc, và khi hàm main kết thúc, simpe bị phá hủy.
Lưu ý: Những biến toàn cục được khởi tạo trước hàm main và phá hủy sau hàm main.
A warning about the exit() function
Chú ý rằng nến bạn sử dụng hàm exit(), chương trình của bạn sẽ kết thúc ngay lập tức và destructor sẽ không bao giờ được gọi. Cần thận với hàm này vì nhiều lúc, bạn cần làm một việc gì đó từ destructor.
Bài tiếp theo: Con trỏ this.