Cách khai báo std :: unique_ptr và công dụng của nó là gì?


95

Tôi cố gắng hiểu cách thức std::unique_ptrhoạt động và tôi đã tìm thấy tài liệu này . Tác giả bắt đầu từ ví dụ sau:

#include <utility>  //declarations of unique_ptr
using std::unique_ptr;
// default construction
unique_ptr<int> up; //creates an empty object
// initialize with an argument
unique_ptr<int> uptr (new int(3));
double *pd= new double;
unique_ptr<double> uptr2 (pd);
// overloaded * and ->
*uptr2 = 23.5;
unique_ptr<std::string> ups (new std::string("hello"));
int len=ups->size();

Điều làm tôi khó hiểu là trong dòng này

unique_ptr<int> uptr (new int(3));

Chúng tôi sử dụng số nguyên làm đối số (giữa dấu ngoặc tròn) và ở đây

unique_ptr<double> uptr2 (pd);

chúng tôi đã sử dụng một con trỏ làm đối số. Liệu nó có bất kỳ sự khác biệt?

Điều mà tôi cũng không rõ là con trỏ được khai báo theo cách này sẽ khác với các con trỏ được khai báo theo cách "bình thường" như thế nào.


13
new int(3)trả về một con trỏ đến cái mới int, giống như pdmột con trỏ đến cái mới double.
David Schwartz

Câu trả lời:


89

Hàm tạo của unique_ptr<T>chấp nhận một con trỏ thô tới một đối tượng có kiểu T(vì vậy, nó chấp nhận a T*).

Trong ví dụ đầu tiên:

unique_ptr<int> uptr (new int(3));

Con trỏ là kết quả của một newbiểu thức, trong khi ở ví dụ thứ hai:

unique_ptr<double> uptr2 (pd);

Con trỏ được lưu trữ trong pdbiến.

Về mặt khái niệm, không có gì thay đổi (bạn đang xây dựng một unique_ptrcon trỏ thô), nhưng cách tiếp cận thứ hai có khả năng nguy hiểm hơn, vì ví dụ, nó sẽ cho phép bạn làm:

unique_ptr<double> uptr2 (pd);
// ...
unique_ptr<double> uptr3 (pd);

Do đó có hai con trỏ duy nhất đóng gói hiệu quả cùng một đối tượng (do đó vi phạm ngữ nghĩa của một con trỏ duy nhất ).

Đây là lý do tại sao biểu mẫu đầu tiên để tạo một con trỏ duy nhất sẽ tốt hơn khi có thể. Lưu ý rằng trong C ++ 14, chúng ta sẽ có thể thực hiện:

unique_ptr<int> p = make_unique<int>(42);

Cái nào vừa rõ ràng vừa an toàn hơn. Bây giờ liên quan đến nghi ngờ này của bạn:

Điều mà tôi cũng không rõ là con trỏ được khai báo theo cách này sẽ khác với các con trỏ được khai báo theo cách "bình thường" như thế nào.

Con trỏ thông minh được cho là mô hình hóa quyền sở hữu đối tượng và tự động xử lý việc hủy đối tượng được trỏ khi con trỏ cuối cùng (thông minh, sở hữu) đến đối tượng đó nằm ngoài phạm vi.

Bằng cách này, bạn không phải nhớ thực hiện deletetrên các đối tượng được cấp phát động - trình hủy của con trỏ thông minh sẽ làm điều đó cho bạn - cũng như không phải lo lắng về việc bạn sẽ không tham khảo một con trỏ (treo lơ lửng) tới một đối tượng đã bị hủy:

{
    unique_ptr<int> p = make_unique<int>(42);
    // Going out of scope...
}
// I did not leak my integer here! The destructor of unique_ptr called delete

Bây giờ unique_ptrlà một con trỏ thông minh mô hình quyền sở hữu duy nhất, có nghĩa là bất kỳ lúc nào trong chương trình của bạn sẽ chỉ có một con trỏ (sở hữu) đến đối tượng trỏ - đó là lý do tại sao unique_ptrkhông thể sao chép.

Miễn là bạn sử dụng con trỏ thông minh theo cách không phá vỡ hợp đồng ngầm mà chúng yêu cầu bạn tuân thủ, bạn sẽ có đảm bảo rằng bộ nhớ sẽ không bị rò rỉ và chính sách sở hữu thích hợp cho đối tượng của bạn sẽ được thực thi. Con trỏ thô không cung cấp cho bạn sự đảm bảo này.


3
Xin chào, tôi không thể hiểu bất cứ điều gì về model object ownership, integer leaktrong mã hoặc enforcing ownership policy for object. Bạn có thể vui lòng đề xuất các chủ đề / tài nguyên để tìm hiểu các khái niệm này không?
Flame of udun vào

1
Tôi không thể sử dụng unique_ptrmà không gặp lỗi:, The text ">" is unexpected. It may be that this token was intended as a template argument list terminator but the name is not known to be a template.mặc dù tôi có #include <utility>#include <memory>. Có lời khuyên nào không?
Ẩn danh

15

Không có sự khác biệt trong cách làm việc trong cả hai khái niệm gán cho unique_ptr.

int* intPtr = new int(3);
unique_ptr<int> uptr (intPtr);

tương tự như

unique_ptr<int> uptr (new int(3));

Ở đây unique_ptr tự động xóa không gian bị chiếm dụng uptr.


Làm thế nào các con trỏ, được khai báo theo cách này sẽ khác với các con trỏ được khai báo theo cách "bình thường".

Nếu bạn tạo một số nguyên trong không gian heap (sử dụng từ khóa mới hoặc malloc ), thì bạn sẽ phải tự xóa bộ nhớ đó (sử dụng xóa hoặc miễn phí tương ứng).

Trong đoạn mã dưới đây,

int* heapInt = new int(5);//initialize int in heap memory
.
.//use heapInt
.
delete heapInt;

Tại đây, bạn sẽ phải xóa heapInt khi sử dụng xong. Nếu nó không bị xóa, thì rò rỉ bộ nhớ sẽ xảy ra.

Để tránh rò rỉ bộ nhớ như vậy, unique_ptr được sử dụng, nơi unique_ptr tự động xóa không gian bị chiếm bởi heapInt khi nó vượt ra khỏi phạm vi. Vì vậy, bạn không cần xóa hoặc miễn phí cho unique_ptr.


10

Con trỏ duy nhất được đảm bảo sẽ phá hủy đối tượng mà chúng quản lý khi chúng đi ra khỏi phạm vi. http://en.cppreference.com/w/cpp/memory/unique_ptr

Trong trường hợp này:

unique_ptr<double> uptr2 (pd);

pdsẽ bị phá hủy khi uptr2đi ra khỏi phạm vi. Điều này tạo điều kiện thuận lợi cho việc quản lý bộ nhớ bằng cách xóa tự động.

Trường hợp của unique_ptr<int> uptr (new int(3));không khác, ngoại trừ con trỏ thô không được gán cho bất kỳ biến nào ở đây.


-1

Từ cppreference , một trong các hàm std::unique_ptrtạo là

rõ ràng unique_ptr (con trỏ p) noexcept;

Vì vậy, để tạo một mới std::unique_ptrlà chuyển một con trỏ đến phương thức khởi tạo của nó.

unique_ptr<int> uptr (new int(3));

Hoặc nó giống như

int *int_ptr = new int(3);
std::unique_ptr<int> uptr (int_ptr);

Điểm khác biệt là bạn không phải dọn dẹp sau khi sử dụng. Nếu bạn không sử dụng std::unique_ptr(con trỏ thông minh), bạn sẽ phải xóa nó như thế này

delete int_ptr;

khi bạn không cần nữa hoặc nó sẽ gây rò rỉ bộ nhớ.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.