Một con trỏ "thô" không được quản lý. Đó là, dòng sau:
SomeKindOfObject *someKindOfObject = new SomeKindOfObject();
... sẽ rò rỉ bộ nhớ nếu việc đi kèm delete
không được thực thi vào thời điểm thích hợp.
auto_ptr
Để giảm thiểu những trường hợp này, std::auto_ptr<>
đã được giới thiệu. Tuy nhiên, do những hạn chế của C ++ trước tiêu chuẩn 2011, tuy nhiên, vẫn rất dễ auto_ptr
bị rò rỉ bộ nhớ. Nó là đủ cho các trường hợp hạn chế, chẳng hạn như điều này, tuy nhiên:
void func() {
std::auto_ptr<SomeKindOfObject> sKOO_ptr(new SomeKindOfObject());
// do some work
// will not leak if you do not copy sKOO_ptr.
}
Một trong những trường hợp sử dụng yếu nhất của nó là trong các thùng chứa. Điều này là do nếu một bản sao của một auto_ptr<>
được tạo ra và bản sao cũ không được thiết lập lại cẩn thận, thì container có thể xóa con trỏ và mất dữ liệu.
unique_ptr
Để thay thế, C ++ 11 đã giới thiệu std::unique_ptr<>
:
void func2() {
std::unique_ptr<SomeKindofObject> sKOO_unique(new SomeKindOfObject());
func3(sKOO_unique); // now func3() owns the pointer and sKOO_unique is no longer valid
}
Như vậy unique_ptr<>
sẽ được làm sạch chính xác, ngay cả khi nó được chuyển giữa các chức năng. Nó thực hiện điều này bằng cách đại diện cho "quyền sở hữu" của con trỏ - "chủ sở hữu" làm sạch nó. Điều này làm cho nó lý tưởng để sử dụng trong các thùng chứa:
std::vector<std::unique_ptr<SomeKindofObject>> sKOO_vector();
Không giống như auto_ptr<>
, unique_ptr<>
được xử lý tốt ở đây và khi vector
thay đổi kích thước, không có đối tượng nào sẽ vô tình bị xóa trong khi các vector
bản sao lưu trữ sao lưu của nó.
shared_ptr
và weak_ptr
unique_ptr<>
chắc chắn là hữu ích, nhưng có những trường hợp bạn muốn hai phần cơ sở mã của mình có thể tham chiếu đến cùng một đối tượng và sao chép con trỏ xung quanh, trong khi vẫn được đảm bảo dọn dẹp đúng cách. Ví dụ, một cây có thể trông như thế này, khi sử dụng std::shared_ptr<>
:
template<class T>
struct Node {
T value;
std::shared_ptr<Node<T>> left;
std::shared_ptr<Node<T>> right;
};
Trong trường hợp này, chúng ta thậm chí có thể giữ nhiều bản sao của một nút gốc và cây sẽ được dọn sạch đúng cách khi tất cả các bản sao của nút gốc bị phá hủy.
Điều này hoạt động bởi vì mỗi cái shared_ptr<>
giữ không chỉ con trỏ đến đối tượng, mà còn đếm số tham chiếu của tất cả các shared_ptr<>
đối tượng tham chiếu đến cùng một con trỏ. Khi một cái mới được tạo ra, số lượng tăng lên. Khi một người bị phá hủy, số lượng đi xuống. Khi số đếm bằng 0, con trỏ là delete
d.
Vì vậy, điều này đưa ra một vấn đề: Các cấu trúc liên kết đôi kết thúc với các tham chiếu tròn. Nói rằng chúng tôi muốn thêm một parent
con trỏ vào cây của chúng tôi Node
:
template<class T>
struct Node {
T value;
std::shared_ptr<Node<T>> parent;
std::shared_ptr<Node<T>> left;
std::shared_ptr<Node<T>> right;
};
Bây giờ, nếu chúng ta loại bỏ một Node
, có một tham chiếu theo chu kỳ cho nó. Nó sẽ không bao giờ là delete
d vì số tham chiếu của nó sẽ không bao giờ bằng không.
Để giải quyết vấn đề này, bạn sử dụng std::weak_ptr<>
:
template<class T>
struct Node {
T value;
std::weak_ptr<Node<T>> parent;
std::shared_ptr<Node<T>> left;
std::shared_ptr<Node<T>> right;
};
Bây giờ, mọi thứ sẽ hoạt động chính xác và việc xóa một nút sẽ không để lại các tham chiếu bị kẹt cho nút cha. Nó làm cho việc đi trên cây phức tạp hơn một chút, tuy nhiên:
std::shared_ptr<Node<T>> parent_of_this = node->parent.lock();
Bằng cách này, bạn có thể khóa một tham chiếu đến nút và bạn có một đảm bảo hợp lý rằng nó sẽ không biến mất trong khi bạn đang làm việc với nó, vì bạn đang giữ một shared_ptr<>
nút.
make_shared
và make_unique
Bây giờ, có một số vấn đề nhỏ với shared_ptr<>
và unique_ptr<>
cần được giải quyết. Hai dòng sau có một vấn đề:
foo_unique(std::unique_ptr<SomeKindofObject>(new SomeKindOfObject()), thrower());
foo_shared(std::shared_ptr<SomeKindofObject>(new SomeKindOfObject()), thrower());
Nếu thrower()
ném một ngoại lệ, cả hai dòng sẽ rò rỉ bộ nhớ. Và hơn thế nữa, shared_ptr<>
giữ số tham chiếu cách xa đối tượng mà nó trỏ đến và điều này có thể có nghĩa là phân bổ thứ hai). Điều đó thường không được mong muốn.
C ++ 11 cung cấp std::make_shared<>()
và C ++ 14 cung cấp std::make_unique<>()
để giải quyết vấn đề này:
foo_unique(std::make_unique<SomeKindofObject>(), thrower());
foo_shared(std::make_shared<SomeKindofObject>(), thrower());
Bây giờ, trong cả hai trường hợp, ngay cả khi thrower()
ném ngoại lệ, sẽ không có rò rỉ bộ nhớ. Là một phần thưởng, make_shared<>()
có cơ hội tạo số tham chiếu của nó trong cùng một không gian bộ nhớ với đối tượng được quản lý của nó, vừa có thể nhanh hơn vừa có thể tiết kiệm một vài byte bộ nhớ, đồng thời mang lại cho bạn sự đảm bảo an toàn ngoại lệ!
Ghi chú về Qt
Tuy nhiên, cần lưu ý rằng Qt, phải hỗ trợ các trình biên dịch trước C ++ 11, có mô hình thu gom rác riêng: Nhiều người QObject
có một cơ chế trong đó chúng sẽ bị phá hủy đúng cách mà không cần người dùng sử dụng delete
chúng.
Tôi không biết QObject
s sẽ hành xử như thế nào khi được quản lý bởi các con trỏ được quản lý C ++ 11, vì vậy tôi không thể nói đó shared_ptr<QDialog>
là một ý tưởng hay. Tôi không có đủ kinh nghiệm với Qt để nói chắc chắn, nhưng tôi tin rằng Qt5 đã được điều chỉnh cho trường hợp sử dụng này.