07/09/2018, 09:27

Smart Pointer

Đặt vấn đề Chúng ta vẫn biết rằng quản lý bộ nhớ trong C++ là một vấn đề rất khó đòi hỏi phải có kiến thức nhất định và sự phân tích tốt trong quá trình lập trình vì phải tự mình giải phóng những vùng nhớ không còn được dùng nữa.Trên lý thuyết thì giải phóng bộ nhớ là delete đi vùng nhớ mà khi ...

Đặt vấn đề

Chúng ta vẫn biết rằng quản lý bộ nhớ trong C++ là một vấn đề rất khó đòi hỏi phải có kiến thức nhất định và sự phân tích tốt trong quá trình lập trình vì phải tự mình giải phóng những vùng nhớ không còn được dùng nữa.Trên lý thuyết thì giải phóng bộ nhớ là delete đi vùng nhớ mà khi không cần sử dụng đến nó nữa, quản lý bộ nhớ có thực sự khó không? Khi xây dựng đối tượng thì ta chỉ cần thực hiện việc delete những vùng nhớ cấp phát bởi đối tượng đó trong destructor của nó khi không cần sử dụng nữa. Vậy tại sao mọi người lại nghĩ nó phức tạp? Đúng là nó sẽ không phức tạp, cho đến khi vấn đề xuất hiện: "Nếu ta có một tài nguyên được sử dụng bởi nhiều đối tượng (ở nhiều nơi) trong chương trình, thì mình sẽ thực hiện hủy tài nguyên đó ở đâu và huyer khi nào?”. Và đó chính là khi bạn thực sự đối mặt với vấn đề của việc quản lý tài nguyên trong C++.

Giải pháp

Như ta thấy, nếu mỗi tài nguyên của chúng ta chỉ được sử dụng bởi duy nhất một đối tượng, thì ta chỉ việc thực hiện giải phóng tài nguyên đó trong destructor của đối tượng. Nhưng mọi việc sẽ hoàn toàn khác khi ta có những loại tài nguyên chia sẻ, được sử dụng bởi nhiều đối tượng. Khi đó ta sẽ không biết phải thực hiện giải phóng tài nguyên đó ở đâu – hay nói cách khác, ta sẽ không biết đối tượng nào chịu trách nhiệm giải phóng tài nguyên đó. Do vậy, để giải quyết được vấn đề này thì ta phải sử dụng đến một khái niệm mới – Đó là "Quyền sở hữu".

Raw Pointer và quyền sở hữu

Giả sử khi ta có một đối tượng, trong nó có một con trỏ thông thường (Hay còn gọi là raw pointer) trỏ đến một tài nguyên, thì đối tượng này đã có thể sử dụng tài nguyên đó. Trên lý thuyết, ta có thể coi như đối tượng này đã sở hữu tài nguyên (Vì nó được quyền sử dụng). Nhưng thực tết, xét theo đúng khái niệm, thì đối tượng này vẫn chưa thật sự nắm "Quyền sở hữu". Bởi vì "Quyền sở hữu" bao gồm cả quyền sử dụng và nghĩa vụ hủy. Nó chỉ mới có quyền sử dụng, chứ chưa thực hiện nghĩa vụ hủy đi tài nguyên đó khi nó không còn được sử dụng nữa. Do vậy, chúng ta cần tới một loại con trỏ mới, thông minh hơn, để có thể giúp các đối tượng thật sự nắm được "Quyền sở hữu" – bằng cách thực hiện nghĩa vụ hủy đi tài nguyên khi nó không còn được sử dụng nữa. Và đó cũng chính là lý do mà "Smart pointer" đã ra đời.

Smart Pointer trong C++

Smart pointer là một loại dữ liệu để giả lập các raw pointer, và bổ sung thêm các khả năng mà raw pointer không có như tự động quản lý bộ nhớ, kiểm tra truy xuất ngoài vùng được cấp phát… Để đạt được điều này, người thiết kế C++ đã tạo ra các đối tượng để "đóng gói” các raw pointer (Các đối tượng này còn được gọi là proxy object, được thiết kế dựa theo proxy pattern). Để các đối tượng này hành xử như một raw pointer thật sự, thì các toán tử đặc trưng của pointer thông thường như " * "  và " → "  đều được overload lại trong các đối tượng này. Nhờ vậy mà nó trông rất giống các raw pointer, và ta có thể sử dụng nó như là một raw pointer thật sự. Ngoài ra thì nó còn được định nghĩa để tự động xử lý các vấn đề liên quan đến quản lý bộ nhớ tự động. Cách mà các smart pointer trong C++ hoạt động, để thực hiện việc tự động quản lý tài nguyên, đều dựa vào khái niệm "Quyền sở hữu tài nguyên". Do đó trong C++ có 3 loại smart pointer chính là: Unique_ptr: Loại smart pointer đại diện cho "Quyền sở hữu duy nhất". Nghĩa là tài nguyên mà unique_ptr trỏ tới chỉ có thể được sở hữu bởi duy nhất một đối tượng. Shared_ptr: Loại smart pointer này đại diện cho "Quyền sở hữu chia sẻ". Nghĩa là tài nguyên mà shared_ptr trỏ tới là tài nguyên chia sẻ, có thể được sở hữu bởi nhiều đối tượng. Weak_ptr: Loại smart pointer này đại diện cho "Quyền sở hữu yếu". Nghĩa là đối tượng nắm trong tay weak_ptr trỏ tới một tài nguyên thì nó chỉ có quyền được sử dụng tài nguyên đó, chứ không có quyền và nghĩa vụ hủy đi tài nguyên. Các loại smart pointer trên xuất hiện từ C++11, từ trước C++11 vẫn có một loại smart pointer nữa đó là auto_ptr. Nhưng loại pointer này không còn được khuyến khích sử dụng nữa, vì nó không định nghĩa hành vi "Chuyển quyền sở hữu" một cách rõ ràng, và dễ dàng gây ra lỗi nếu người dùng không nắm rõ nó. Do vậy nó đã được thay bằng unique_ptr kể từ C++11 (Nhưng auto_ptr vẫn còn giữ lại trong C++11 để đảm bảo tính tương thích ngược).

Kết luận

C++11 có 3 loại smart pointers để phục vụ cho những mục đích khác nhau, do đó, tùy vào cách mà tài nguyên đó được sở hữu ta sẽ lựa chọn loại smart pointer phù hợp để tránh lỗi cũng như giảm thiểu chi phí khi sử dụng nó. Do vậy, việc hiểu rõ về smart pointer và cách nó hoạt động là rất cần thiết để sử dụng đúng cách và hiệu quả.

0