Menu
CoddyTech

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ớ.

challenge icon

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ớp Session đạ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ên activityLog để theo dõi hoạt động của người dùng, cùng với logSize để 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
  • 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ột Session được cấp phát trên stack với username đầu vào, session ID 101 và kích thước nhật ký đầu vào. Ghi lại hai hoạt động với mã 1 và 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ằng new với username "Guest", session ID 202 và 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ủ động delete đối tượng.
    • Sau khi xóa, in ra "Heap session destroyed"

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;
}
quiz iconTự kiểm tra

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

Tự luyện tập: Trình biên dịch C++ online