Quyết định sử dụng con trỏ thông minh nào là câu hỏi về quyền sở hữu . Khi nói đến quản lý tài nguyên, đối tượng A sở hữu đối tượng B nếu nó kiểm soát vòng đời của đối tượng B. Ví dụ, các biến thành viên được sở hữu bởi các đối tượng tương ứng của chúng vì thời gian tồn tại của các biến thành viên được gắn với vòng đời của đối tượng. Bạn chọn con trỏ thông minh dựa trên cách đối tượng được sở hữu.
Lưu ý rằng quyền sở hữu trong một hệ thống phần mềm tách biệt với quyền sở hữu như chúng ta sẽ nghĩ về nó bên ngoài phần mềm. Ví dụ, một người có thể "sở hữu" nhà của họ, nhưng điều đó không nhất thiết có nghĩa là một Person
đối tượng có quyền kiểm soát vòng đời của một House
đối tượng. Kết hợp các khái niệm trong thế giới thực này với các khái niệm phần mềm là một cách chắc chắn để tự lập trình vào một lỗ hổng.
Nếu bạn có quyền sở hữu duy nhất của đối tượng, sử dụng std::unique_ptr<T>
.
Nếu bạn đã chia sẻ quyền sở hữu đối tượng ...
- Nếu không có chu kỳ sở hữu, hãy sử dụng std::shared_ptr<T>
.
- Nếu có chu kỳ, xác định "hướng" và sử dụng std::shared_ptr<T>
theo hướng này và hướng std::weak_ptr<T>
khác.
Nếu đối tượng sở hữu bạn, nhưng có khả năng không có chủ sở hữu, hãy sử dụng các con trỏ bình thường T*
(ví dụ: con trỏ cha mẹ).
Nếu đối tượng sở hữu bạn (hoặc nếu không có sự tồn tại được đảm bảo), hãy sử dụng tài liệu tham khảo T&
.
Hãy cẩn thận: Hãy nhận biết các chi phí của con trỏ thông minh. Trong môi trường hạn chế về bộ nhớ hoặc hiệu năng, có thể có ích khi chỉ sử dụng các con trỏ bình thường với sơ đồ thủ công hơn để quản lý bộ nhớ.
Các chi phí:
- Nếu bạn có một deleter tùy chỉnh (ví dụ: bạn sử dụng nhóm phân bổ) thì điều này sẽ phát sinh chi phí trên mỗi con trỏ có thể dễ dàng tránh được bằng cách xóa thủ công.
std::shared_ptr
có chi phí gia tăng số tham chiếu trên bản sao, cộng với sự giảm dần về sự phá hủy, sau đó là kiểm tra số 0 với việc xóa đối tượng bị giữ. Tùy thuộc vào việc triển khai, điều này có thể làm sai mã của bạn và gây ra các vấn đề về hiệu suất.
- Thời gian biên dịch. Như với tất cả các mẫu, con trỏ thông minh đóng góp tiêu cực vào thời gian biên dịch.
Ví dụ:
struct BinaryTree
{
Tree* m_parent;
std::unique_ptr<BinaryTree> m_children[2]; // or use std::array...
};
Cây nhị phân không sở hữu cha mẹ của nó, nhưng sự tồn tại của cây ngụ ý sự tồn tại của cha mẹ của nó (hoặc nullptr
cho gốc), do đó sử dụng một con trỏ bình thường. Cây nhị phân (có giá trị ngữ nghĩa) có quyền sở hữu duy nhất đối với con của nó, vì vậy chúng là std::unique_ptr
.
struct ListNode
{
std::shared_ptr<ListNode> m_next;
std::weak_ptr<ListNode> m_prev;
};
Ở đây, nút danh sách sở hữu các danh sách tiếp theo và trước đó, vì vậy chúng tôi xác định hướng và sử dụng shared_ptr
cho tiếp theo và weak_ptr
trước để phá vỡ chu kỳ.