Cấp resource ở đâu, giải phóng ở đó

Khi làm việc với low-level resource (memory, file descriptor, mutex, network socket, GPU texture) trong ngôn ngữ không có garbage collector tự quản — C, C++, Rust ở mức unsafe — một quy tắc đơn giản nhưng cứu nhiều giờ debug: resource được cấp ở đâu thì ở đó chịu trách nhiệm giải phóng. Quy tắc này là phát biểu dân dụng của hai khái niệm lớn trong C++: RAII và ownership.

Vì sao chia resource ownership ra nhiều nơi dẫn tới bug

Khi resource được cấp ở class A và giải phóng ở class B, có ba loại bug khó tránh:

Triệu chứng phổ biến nhất là segfault xảy ra ở chỗ tưởng chừng không liên quan — vì tại điểm crash, code chỉ là nạn nhân, thủ phạm là một free sai chỗ ở xa.

RAII — ràng buộc resource vào object lifecycle

C++ giải bài toán này bằng RAII (Resource Acquisition Is Initialization), thuật ngữ do Bjarne Stroustrup đặt khi cùng Andrew Koenig phát triển kỹ thuật trong giai đoạn 1984–1989 cho exception-safe resource management. Ý tưởng: acquire resource trong constructor, release trong destructor. Object còn sống thì resource còn được giữ; object bị huỷ là resource tự động được trả lại.

Hệ quả: class invariant ràng buộc “object exists” với “resource is held”. Nếu không có object leak thì không có resource leak — Wikipedia diễn đạt là “if there are no object leaks, there are no resource leaks”. Stroustrup cũng nói: “In realistic systems, there are far more resource acquisitions than kinds of resources, so the ‘resource acquisition is initialization’ technique leads to less code than use of a ‘finally’ construct.”

std::unique_ptr<T, Deleter> là hiện thân chuẩn của RAII cho memory. Có thể truyền custom deleter cho resource ngoài memory:

std::unique_ptr<SDL_Texture, decltype(&SDL_DestroyTexture)>
    texture(SDL_CreateTexture(...), SDL_DestroyTexture);

Texture bị SDL_DestroyTexture tự động khi texture ra khỏi scope — không cần viết Exit() thủ công và không thể quên.

Single ownership và transfer

RAII chỉ làm việc khi mỗi resource có một owner duy nhất. Nhiều owner cùng nghĩ mình phải free là gốc rễ của double-free. Hai cách để giữ single ownership:

Rust nâng single ownership lên thành first-class với borrow checker — bắt compiler kiểm tra ownership tại compile time thay vì để bug rò rỉ ra runtime. Đây là cùng tư duy của Stroustrup, nhưng được ép thi hành thay vì khuyến cáo.

Khi không có ngôn ngữ giúp

Trong C thuần và trong wrapper C của SDL/OpenGL/Vulkan, không có destructor — phải tự kỷ luật. Quy tắc thực dụng để giữ ownership rõ:

Trải nghiệm cá nhân

Quy tắc này được rút ra từ một bug segfault khi viết snake game bằng SDL2 thời 2016–2017 (repo sdl_game/snake). Texture được cấp ở một state, một state khác cố giải phóng → segfault ở runtime khi user chuyển màn hình. Phản ứng đầu tiên là comment-out toàn bộ delete trong LogoState::Exit (snake/LogoState.cpp:95-100) — tránh bug nhưng đổi lấy memory leak. Bài học sau cùng đọng lại: resource được cấp ở đâu thì ở đó chịu trách nhiệm giải phóng — đây là lưu ý vô cùng quan trọng khi làm các dự án với low-level memory API như C. Chi tiết trong transcript phỏng vấn.

Cập nhật: 2026-05-29