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óngCon 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.
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ớpConnectionquả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
connectionIdriêng tư (chuỗi) để định danh kết nối - Thuộc tính
isOpenriê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
isOpenthà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
isOpenthà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
- Thuộc tính
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
Connectiontrê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"
- Tạo một phạm vi bằng dấu ngoặc nhọn
Đ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;
}
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