Menu
CoddyTech

RAII trong C++

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 16 trên 104.

RAII (Resource Acquisition Is Initialization) là một thành ngữ nền tảng trong C++, gắn việc quản lý tài nguyên với vòng đời của đối tượng. Ý tưởng rất đơn giản: nhận tài nguyên trong hàm khởi tạo, giải phóng tài nguyên trong hàm hủy.

Khi một đối tượng được tạo, nó có được mọi tài nguyên cần thiết. Khi đối tượng ra khỏi phạm vi, hàm hủy của nó tự động giải phóng các tài nguyên đó. Điều này đảm bảo việc dọn dẹp ngay cả khi xảy ra ngoại lệ.

class FileHandler {
    std::FILE* file;
public:
    FileHandler(const char* filename) {
        file = std::fopen(filename, "r");  // Tiếp nhận tài nguyên
    }
    
    ~FileHandler() {
        if (file) std::fclose(file);       // Giải phóng tài nguyên
    }
};

Với RAII, bạn không bao giờ quên dọn dẹp:

void processFile() {
    FileHandler handler("data.txt");  // Đã lấy tài nguyên
    // Thao tác với tệp...
    // Nếu có ngoại lệ (exception) ở đây, hàm hủy vẫn chạy!
}   // Tài nguyên tự động được giải phóng

Con trỏ thông minh (smart pointer) là ví dụ hoàn hảo về RAII trong thực tế. unique_ptr nhận quyền sở hữu bộ nhớ heap trong hàm khởi tạo và giải phóng bộ nhớ đó trong hàm hủy. Bạn đã dùng RAII mà không nhận ra đấy!

RAII áp dụng cho mọi tài nguyên: bộ nhớ, bộ xử lý tệp, kết nối mạng, mutex hoặc kết nối cơ sở dữ liệu. Bằng cách bọc tài nguyên trong các đối tượng, C++ đảm bảo việc dọn dẹp diễn ra một cách xác định và loại bỏ nhiều loại bug như rò rỉ tài nguyên và bộ xử lý bị treo.

challenge icon

Thử thách

Dễ

Hãy xây dựng một trình quản lý kết nối minh họa RAII bằng cách tự động xử lý vòng đời của các kết nối kiểu mạng. Khi tạo kết nối, nó sẽ “mở”, còn khi ra khỏi phạm vi, nó sẽ tự động “đóng”: không cần dọn dẹp thủ công!

Bạn sẽ tạo hai tệp để sắp xếp code:

  • Connection.h: Định nghĩa lớp Connection quản lý tài nguyên kết nối theo các nguyên tắc RAII. Lớp này cần có:
    • Thuộc tính connectionId riêng tư (chuỗi) để định danh kết nối
    • Thuộc tính isOpen riêng tư (bool) để theo dõi trạng thái kết nối
    • Một hàm khởi tạo nhận mã kết nối, đặt isOpen thành true và in ra "Opening connection: <id>"
    • Một hàm hủy kiểm tra xem kết nối có đang mở không, đóng kết nối bằng cách đặt isOpen thành false và in ra "Closing connection: <id>"
    • Một phương thức send(string message) in ra "[<id>] Sending: <message>" nếu kết nối đang mở
    • Một phương thức getId() trả về mã kết nối
  • main.cpp: Minh họa cách RAII đảm bảo tự động dọn dẹp bằng cách đọc mã kết nối và một tin nhắn từ đầu vào. Sau đó:
    • Tạo một phạm vi bằng dấu ngoặc nhọn { }
    • Bên trong phạm vi đó, tạo một đối tượng Connection trên ngăn xếp bằng mã đầu vào
    • Dùng kết nối để gửi tin nhắn đầu vào
    • Để phạm vi kết thúc, khi đó kết nối sẽ tự động đóng
    • Sau phạm vi đó, in ra "Connection out of scope"

Điểm hay của RAII là bạn không cần nhớ đóng kết nối: hàm hủy tự động xử lý việc đó khi đối tượng ra khỏi phạm vi. Mẫu này ngăn rò rỉ tài nguyên ngay cả khi có điều gì bất ngờ xảy ra trong code.

Đưa tệp tiêu đề vào main.cpp bằng #include "Connection.h".

Tự làm thử

#include <iostream>
#include <string>
#include "Connection.h"

using namespace std;

int main() {
    string connectionId;
    string message;
    
    // Đọc đầu vào
    cin >> connectionId;
    cin.ignore();
    getline(cin, message);

    // TODO: Tạo một phạm vi (scope) bằng dấu ngoặc nhọn { }
    // Trong phạm vi đó:
    //   - Tạo một đối tượng (object) Connection trên stack bằng connectionId
    //   - Dùng kết nối để gửi thông điệp
    // Để phạm vi kết thúc (hàm hủy sẽ tự động được gọi)

    // TODO: Sau khi phạm vi kết thúc, in ra "Connection out of scope"

    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