Thông qua từ đây .
Hầu hết các mẫu trong thư viện chuẩn C ++ yêu cầu chúng phải được khởi tạo với các kiểu hoàn chỉnh. Tuy nhiên shared_ptr
và unique_ptr
là một phần ngoại lệ. Một số, nhưng không phải tất cả các thành viên của họ có thể được khởi tạo với các loại không đầy đủ. Động lực cho việc này là để hỗ trợ các thành ngữ như pimpl sử dụng con trỏ thông minh và không có nguy cơ hành vi không xác định.
Hành vi không xác định có thể xảy ra khi bạn có một loại không đầy đủ và bạn gọi delete
nó:
class A;
A* a = ...;
delete a;
Trên đây là mã hợp pháp. Nó sẽ biên dịch. Trình biên dịch của bạn có thể hoặc không thể phát ra cảnh báo cho mã ở trên như ở trên. Khi nó thực thi, những điều xấu có thể sẽ xảy ra. Nếu bạn rất may mắn chương trình của bạn sẽ sụp đổ. Tuy nhiên, một kết quả có thể xảy ra hơn là chương trình của bạn sẽ âm thầm rò rỉ bộ nhớ như ~A()
sẽ không được gọi.
Sử dụng auto_ptr<A>
trong ví dụ trên không giúp được gì. Bạn vẫn nhận được hành vi không xác định giống như bạn đã sử dụng một con trỏ thô.
Tuy nhiên, sử dụng các lớp không đầy đủ ở một số nơi là rất hữu ích! Đây là nơi shared_ptr
và unique_ptr
giúp đỡ. Việc sử dụng một trong những con trỏ thông minh này sẽ cho phép bạn thoát khỏi một loại không hoàn chỉnh, trừ khi cần phải có một loại hoàn chỉnh. Và quan trọng nhất, khi cần phải có một loại hoàn chỉnh, bạn sẽ gặp lỗi thời gian biên dịch nếu bạn cố gắng sử dụng con trỏ thông minh với một loại không đầy đủ tại thời điểm đó.
Không còn hành vi không xác định:
Nếu mã của bạn biên dịch, thì bạn đã sử dụng một loại hoàn chỉnh ở mọi nơi bạn cần.
class A
{
class impl;
std::unique_ptr<impl> ptr_; // ok!
public:
A();
~A();
// ...
};
shared_ptr
và unique_ptr
yêu cầu một loại hoàn chỉnh ở những nơi khác nhau. Những lý do rất mơ hồ, phải làm với một deleter động so với một deleter tĩnh. Những lý do chính xác không quan trọng. Trong thực tế, trong hầu hết các mã, điều thực sự không quan trọng đối với bạn để biết chính xác nơi cần một loại hoàn chỉnh. Chỉ cần mã, và nếu bạn nhận được nó sai, trình biên dịch sẽ cho bạn biết.
Tuy nhiên, trong trường hợp nó hữu ích cho bạn, đây là bảng ghi lại một số thành viên shared_ptr
và unique_ptr
liên quan đến các yêu cầu về tính đầy đủ. Nếu thành viên yêu cầu một loại hoàn chỉnh, thì mục nhập có "C", nếu không, mục nhập bảng được điền "I".
Complete type requirements for unique_ptr and shared_ptr
unique_ptr shared_ptr
+------------------------+---------------+---------------+
| P() | I | I |
| default constructor | | |
+------------------------+---------------+---------------+
| P(const P&) | N/A | I |
| copy constructor | | |
+------------------------+---------------+---------------+
| P(P&&) | I | I |
| move constructor | | |
+------------------------+---------------+---------------+
| ~P() | C | I |
| destructor | | |
+------------------------+---------------+---------------+
| P(A*) | I | C |
+------------------------+---------------+---------------+
| operator=(const P&) | N/A | I |
| copy assignment | | |
+------------------------+---------------+---------------+
| operator=(P&&) | C | I |
| move assignment | | |
+------------------------+---------------+---------------+
| reset() | C | I |
+------------------------+---------------+---------------+
| reset(A*) | C | C |
+------------------------+---------------+---------------+
Bất kỳ hoạt động nào yêu cầu chuyển đổi con trỏ đều yêu cầu các loại hoàn chỉnh cho cả hai unique_ptr
và shared_ptr
.
Hàm unique_ptr<A>{A*}
tạo có thể thoát khỏi chỉ với phần chưa hoàn thành A
nếu trình biên dịch không bắt buộc phải thiết lập lệnh gọi đến ~unique_ptr<A>()
. Ví dụ, nếu bạn đặt unique_ptr
heap, bạn có thể thoát khỏi một cách không đầy đủ A
. Thông tin chi tiết về điểm này có thể được tìm thấy trong câu trả lời của BarryTheHatchet tại đây .
shared_ptr
/unique_ptr
" Bảng ở cuối sẽ trả lời câu hỏi của bạn.