Tìm hiểu sâu về hàm hủy
Thuộc phần Lập trình hướng đối tượng trong hành trình C++ của Coddy. Bài học 24 trên 104.
Hàm hủy (destructor) là một hàm thành viên đặc biệt, tự động chạy khi một đối tượng bị hủy. Nhiệm vụ chính của hàm này là giải phóng các tài nguyên mà đối tượng đã lấy trong suốt vòng đời, chẳng hạn như bộ nhớ được cấp phát động, định danh tệp hoặc kết nối mạng.
Hàm hủy có cùng tên với lớp, thêm dấu ngã ~ ở phía trước. Hàm này không nhận tham số và không có kiểu trả về:
class FileHandler {
std::string filename;
int* buffer;
public:
FileHandler(std::string name, size_t size)
: filename(name), buffer(new int[size]) {
std::cout << "Opening " << filename << "\n";
}
~FileHandler() {
delete[] buffer;
std::cout << "Closing " << filename << "\n";
}
};Hàm hủy được gọi tự động trong những trường hợp sau:
- Các đối tượng trên ngăn xếp ra khỏi phạm vi
- Hàm
deleteđược gọi trên một đối tượng được cấp phát trên heap - Vòng đời của một đối tượng tạm thời kết thúc
void example() {
FileHandler f1("data.txt", 100); // Gọi hàm khởi tạo
FileHandler* f2 = new FileHandler("log.txt", 50);
delete f2; // Gọi hàm hủy cho f2
} // Gọi hàm hủy cho f1 (ra khỏi phạm vi)Khác với hàm khởi tạo, một lớp chỉ có thể có một hàm hủy. Bạn không thể nạp chồng hàm này.
Nếu bạn không định nghĩa hàm hủy, trình biên dịch sẽ tạo một hàm hủy mặc định không làm gì đặc biệt, chỉ đơn giản là hủy từng thành viên. Với các lớp quản lý tài nguyên, bạn phải tự viết hàm hủy để tránh rò rỉ bộ nhớ.
Thử thách
DễHãy xây dựng một trình quản lý phiên để theo dõi các phiên người dùng đang hoạt động và minh họa cách hàm hủy tự động giải phóng tài nguyên khi đối tượng bị hủy, dù đối tượng rời khỏi phạm vi hay được xóa rõ ràng.
Bạn sẽ tạo hai tệp để sắp xếp code:
Session.h: Định nghĩa lớpSessionđại diện cho một phiên người dùng đang hoạt động với dữ liệu được cấp phát động. Lớp của bạn cần có:- Các thành viên riêng tư:
username(chuỗi),sessionId(int) và con trỏ đến một mảng số nguyên có tênactivityLogđể theo dõi hoạt động của người dùng, cùng vớilogSizeđể lưu kích thước mảng - Một hàm khởi tạo nhận username, session ID và kích thước nhật ký. Hàm này cần cấp phát mảng nhật ký hoạt động trên heap, khởi tạo mọi phần tử thành 0 và in ra
"Session <sessionId> started for <username>" - Một hàm hủy giải phóng bộ nhớ đã cấp phát và in ra
"Session <sessionId> ended for <username>" - Một phương thức
logActivity(int activityCode)lưu mã hoạt động vào vị trí còn trống tiếp theo (theo dõi vị trí hiện tại bên trong lớp); nếu nhật ký đã đầy, lệnh gọi sẽ bị bỏ qua, vì vậy số lượng không bao giờ vượt quá kích thước nhật ký - Một phương thức
getActivityCount()trả về số hoạt động đã được ghi lại - Một phương thức
getUsername()trả về username
- Các thành viên riêng tư:
main.cpp: Minh họa cách hàm hủy hoạt động trong các tình huống khác nhau. Đọc username và kích thước nhật ký từ đầu vào (mỗi giá trị trên một dòng riêng), sau đó:- Tạo một khối lệnh phạm vi bằng dấu ngoặc nhọn
{ }và bên trong khối, tạo mộtSessionđược cấp phát trên stack với username đầu vào, session ID101và kích thước nhật ký đầu vào. Ghi lại hai hoạt động với mã1và2, sau đó in ra"Stack session activities: <count>". Khi khối kết thúc, hàm hủy sẽ tự động chạy. - Sau khối lệnh, in ra
"Stack session destroyed" - Tạo một
Sessionđược cấp phát trên heap bằngnewvới username"Guest", session ID202và kích thước nhật ký3. Ghi lại một hoạt động với mã5, in ra"Heap session activities: <count>", sau đó chủ độngdeleteđối tượng. - Sau khi xóa, in ra
"Heap session destroyed"
- Tạo một khối lệnh phạm vi bằng dấu ngoặc nhọn
Thử thách này minh họa hai cách chính để hàm hủy được gọi: tự động khi đối tượng trên stack rời khỏi phạm vi và thủ công khi bạn xóa đối tượng trên heap. Cả hai cách đều cần giải phóng đúng cách nhật ký hoạt động được cấp phát động để tránh rò rỉ bộ nhớ.
Thêm tệp tiêu đề vào main.cpp bằng #include "Session.h".
Tự làm thử
#include <iostream>
#include <string>
#include "Session.h"
using namespace std;
int main() {
// Đọc dữ liệu đầu vào (input)
string username;
int logSize;
getline(cin, username);
cin >> logSize;
// TODO: Tạo một khối lệnh (block) có phạm vi (scope) riêng bằng dấu ngoặc nhọn { }
// Bên trong khối lệnh:
// - Tạo một Session trên ngăn xếp với username đầu vào, ID phiên 101 và kích thước nhật ký đầu vào
// - Ghi lại hai hoạt động có mã 1 và 2
// - In ra (print) "Stack session activities: <count>"
// Khi khối lệnh kết thúc, hàm hủy tự động chạy
// TODO: Sau khối lệnh, in ra "Stack session destroyed"
// TODO: Tạo một Session trên vùng nhớ heap bằng new
// - Username: "Guest", ID phiên: 202, kích thước nhật ký: 3
// - Ghi lại một hoạt động có mã 5
// - In ra (print) "Heap session activities: <count>"
// - Xóa phiên
// TODO: Sau khi xóa, in ra "Heap session destroyed"
return 0;
}
Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.
Tất cả bài học trong Lập trình hướng đối tượng
1Nền tảng về OOP
Tệp bên ngoàiBiên dịch và tạo bản dựng C++Tệp header và tệp nguồnNamespace và phạm viGiới thiệu về OOP trong C++Lớp và đối tượngCon trỏ thisPhương thức (hàm thành viên)Thuộc tính (dữ liệu thành viên)Cơ bản về hàm khởi tạo và hàm hủyÔn tập: máy tính đơn giản4Thuộc tính của lớp
Thành viên đối tượng và thành viên tĩnhHàm truy xuất và hàm thiết lậpHàm thành viên hằngTừ khóa mutablePhương thức và biến tĩnhHàm và lớp bạnÔn tập: Trình quản lý tài khoản ngân hàng7Kế thừa
Kế thừa cơ bảnMức truy cập trong kế thừaThứ tự gọi hàm khởi tạo và hàm hủyGhi đè phương thứcHàm ảo và VTableĐa kế thừaKế thừa ảoÔn tập: hệ phân cấp nhân viên2Quản lý bộ nhớ
Bộ nhớ ngăn xếp và heapCon trỏ và tham chiếuBộ nhớ động (new/delete)Con trỏ thông minh trong C++RAII trong C++Ôn tập: Trình quản lý mảng động5Tính đóng gói
Phạm vi truy cập trong C++Tìm hiểu sâu về phạm vi truy cậpẨn thông tinStruct và lớpLớp lồng nhau và lớp bên trongÔn tập: hệ thống hồ sơ sinh viên8Đa hình
Đa hình lúc biên dịch và khi chạyNạp chồng hàmÔn lại hàm ảoHàm ảo thuần túyLớp trừu tượngThiết kế interface trong C++Ép kiểu động và RTTIÔn tập: Máy tính hình học3Hàm khởi tạo và hàm hủy
Hàm khởi tạo mặc địnhHàm khởi tạo có tham sốHàm khởi tạo sao chépHàm khởi tạo di chuyểnDanh sách khởi tạoHàm khởi tạo ủy quyềnTìm hiểu sâu về hàm hủyQuy tắc 3 / 5 / 0Ôn tập: lớp chuỗi6Nạp chồng toán tử
Giới thiệu về nạp chồng toán tửNạp chồng toán tử số họcNạp chồng toán tử so sánhToán tử luồngNạp chồng toán tử gánNạp chồng toán tử [] và ()Toán tử chuyển đổi kiểuÔn tập: lớp ma trận9Mẫu
Mẫu hàmMẫu lớpChuyên biệt hóa mẫuMẫu biến tham sốCơ bản về SFINAE và đặc điểm kiểuÔn tập: vùng chứa tổng quátTự luyện tập: Trình biên dịch C++ online