con trỏ thông minh (boost) giải thích


220

Sự khác biệt giữa các bộ con trỏ sau đây là gì? Khi nào bạn sử dụng mỗi con trỏ trong mã sản xuất, nếu có?

Ví dụ sẽ được đánh giá cao!

  1. scoped_ptr

  2. shared_ptr

  3. weak_ptr

  4. intrusive_ptr

Bạn có sử dụng boost trong mã sản xuất?

Câu trả lời:


339

Tính chất cơ bản của con trỏ thông minh

Thật dễ dàng khi bạn có các thuộc tính mà bạn có thể gán từng con trỏ thông minh. Có ba tính chất quan trọng.

  • không có quyền sở hữu
  • chuyển quyền sở hữu
  • chia sẻ quyền sở hữu

Đầu tiên có nghĩa là một con trỏ thông minh không thể xóa đối tượng, vì nó không sở hữu nó. Thứ hai có nghĩa là chỉ một con trỏ thông minh có thể trỏ đến cùng một đối tượng cùng một lúc. Nếu con trỏ thông minh được trả về từ các hàm, ví dụ quyền sở hữu được chuyển sang con trỏ thông minh được trả về.

Thứ ba có nghĩa là nhiều con trỏ thông minh có thể trỏ đến cùng một đối tượng cùng một lúc. Điều này cũng áp dụng cho một con trỏ thô , tuy nhiên con trỏ thô thiếu một tính năng quan trọng: Chúng không xác định liệu chúng có sở hữu hay không. Một phần của quyền sở hữu con trỏ thông minh sẽ xóa đối tượng nếu mọi chủ sở hữu từ bỏ đối tượng. Hành vi này xảy ra là cần thiết thường xuyên, vì vậy chia sẻ sở hữu con trỏ thông minh được lan truyền rộng rãi.

Một số sở hữu con trỏ thông minh hỗ trợ cả thứ hai và thứ ba. Do đó, chúng không thể được trả về từ các chức năng hoặc được chuyển qua một nơi khác. Cái nào phù hợp nhất cho các RAIImục đích mà con trỏ thông minh được giữ cục bộ và chỉ được tạo để nó giải phóng một đối tượng sau khi nó vượt ra khỏi phạm vi.

Chia sẻ quyền sở hữu có thể được thực hiện bằng cách có một nhà xây dựng sao chép. Điều này tự nhiên sao chép một con trỏ thông minh và cả bản sao và bản gốc sẽ tham chiếu cùng một đối tượng. Hiện tại, việc chuyển quyền sở hữu thực sự không thể được thực hiện trong C ++, vì không có cách nào để chuyển thứ gì đó từ đối tượng này sang đối tượng khác được hỗ trợ bởi ngôn ngữ: Nếu bạn cố gắng trả lại một đối tượng từ một chức năng, điều đang xảy ra là đối tượng được sao chép. Vì vậy, một con trỏ thông minh thực hiện chuyển quyền sở hữu phải sử dụng hàm tạo sao chép để thực hiện chuyển quyền sở hữu đó. Tuy nhiên, điều này đến lượt nó phá vỡ việc sử dụng nó trong các container, bởi vì các yêu cầu nêu một hành vi nhất định của hàm tạo sao chép của các phần tử của các container không tương thích với hành vi được gọi là "hàm tạo di chuyển" này của các con trỏ thông minh này.

C ++ 1x cung cấp hỗ trợ riêng cho chuyển quyền sở hữu bằng cách giới thiệu cái gọi là "nhà xây dựng di chuyển" và "nhà điều hành chuyển nhượng di chuyển". Nó cũng đi kèm với một con trỏ thông minh chuyển quyền sở hữu như vậy được gọi là unique_ptr.

Phân loại con trỏ thông minh

scoped_ptrlà một con trỏ thông minh không thể chuyển nhượng cũng không thể chia sẻ. Nó chỉ có thể sử dụng được nếu bạn cần phân bổ bộ nhớ cục bộ, nhưng hãy chắc chắn rằng nó được giải phóng trở lại khi nó vượt quá phạm vi. Nhưng nó vẫn có thể được hoán đổi với một scoped_ptr khác, nếu bạn muốn làm như vậy.

shared_ptrlà một con trỏ thông minh chia sẻ quyền sở hữu (loại thứ ba ở trên). Nó là tham chiếu được tính để nó có thể nhìn thấy khi bản sao cuối cùng của nó đi ra khỏi phạm vi và sau đó nó giải phóng đối tượng được quản lý.

weak_ptrlà một con trỏ thông minh không sở hữu. Nó được sử dụng để tham chiếu một đối tượng được quản lý (được quản lý bởi shared_ptr) mà không cần thêm số tham chiếu. Thông thường, bạn sẽ cần lấy con trỏ thô ra khỏi shared_ptr và sao chép xung quanh. Nhưng điều đó sẽ không an toàn, vì bạn sẽ không có cách nào để kiểm tra khi đối tượng thực sự bị xóa. Vì vậy, yếu_ptr cung cấp phương tiện bằng cách tham chiếu một đối tượng được chia sẻ bởi shared_ptr. Nếu bạn cần truy cập vào đối tượng, bạn có thể khóa việc quản lý đối tượng đó (để tránh điều đó trong một luồng khác, shared_ptr giải phóng nó trong khi bạn sử dụng đối tượng) và sau đó sử dụng nó. Nếu điểm yếu_ptr trỏ đến một đối tượng đã bị xóa, nó sẽ chú ý đến bạn bằng cách ném một ngoại lệ. Sử dụng yếu_ptr là có lợi nhất khi bạn có một tham chiếu theo chu kỳ: Đếm tham chiếu không thể dễ dàng đối phó với tình huống như vậy.

intrusive_ptrgiống như shared_ptr nhưng nó không giữ số tham chiếu trong shared_ptr mà để tăng / giảm số đếm cho một số hàm trợ giúp cần được xác định bởi đối tượng được quản lý. Điều này có lợi thế là một đối tượng đã được tham chiếu (có số tham chiếu tăng theo cơ chế đếm tham chiếu bên ngoài) có thể được nhồi vào một intrusive_ptr - vì số tham chiếu không còn bên trong con trỏ thông minh, nhưng con trỏ thông minh sử dụng một con trỏ thông minh cơ chế đếm tham chiếu.

unique_ptrlà một sự chuyển giao của con trỏ sở hữu. Bạn không thể sao chép nó, nhưng bạn có thể di chuyển nó bằng cách sử dụng các hàm tạo di chuyển của C ++ 1x:

unique_ptr<type> p(new type);
unique_ptr<type> q(p); // not legal!
unique_ptr<type> r(move(p)); // legal. p is now empty, but r owns the object
unique_ptr<type> s(function_returning_a_unique_ptr()); // legal!

Đây là ngữ nghĩa mà std :: auto_ptr tuân theo, nhưng vì thiếu hỗ trợ riêng cho việc di chuyển, nó không cung cấp cho họ mà không gặp khó khăn. unique_ptr sẽ tự động đánh cắp tài nguyên từ một unique_ptr tạm thời khác, đây là một trong những tính năng chính của ngữ nghĩa di chuyển. auto_ptr sẽ không được chấp nhận trong phiên bản C ++ Standard tiếp theo có lợi cho unique_ptr. C ++ 1x cũng sẽ cho phép nhồi các đối tượng chỉ có thể di chuyển nhưng không thể sao chép vào các thùng chứa. Vì vậy, bạn có thể nhét unique_ptr vào một vectơ chẳng hạn. Tôi sẽ dừng ở đây và giới thiệu cho bạn một bài viết hay về điều này nếu bạn muốn đọc thêm về điều này.


3
cảm ơn các anh chàng khen ngợi. tôi đánh giá cao vì vậy bạn cũng sẽ nhận được +1 ngay bây giờ: p
Johannes Schaub - litb

@litb: Tôi nghi ngờ về "chuyển quyền sở hữu"; Tôi đồng ý rằng không có sự chuyển giao quyền sở hữu thực sự giữa các đối tượng trong C ++ 03, nhưng đối với các con trỏ thông minh thì điều này không thể thực hiện được, bởi cơ chế sao chép phá hủy được nêu ở đây notifyit.com/articles/article.aspx?p=31529&seqNum= 5 .
huyền thoại2k

3
câu trả lời tuyệt vời. Lưu ý: auto_ptrđã bị phản đối (C ++ 11).
nickolay

2
"điều này đến lượt nó phá vỡ việc sử dụng nó trong các container, bởi vì các yêu cầu nêu một hành vi nhất định của hàm tạo sao chép của các phần tử của các container không tương thích với hành vi được gọi là" constructor di chuyển "này của các con trỏ thông minh này." Không có được phần đó.
Raja

Tôi cũng đã được thông báo rằng intrusive_ptrcó thể thích hợp hơn để shared_ptrkết hợp bộ nhớ cache tốt hơn. Rõ ràng bộ đệm hoạt động tốt hơn nếu bạn lưu trữ số tham chiếu như một phần của bộ nhớ của chính đối tượng được quản lý thay vì một đối tượng riêng biệt. Điều này có thể được thực hiện trong một mẫu hoặc siêu lớp của đối tượng được quản lý.
Eliot

91

scoped_ptr là đơn giản nhất. Khi nó đi ra khỏi phạm vi, nó bị phá hủy. Mã sau đây là bất hợp pháp (scoped_ptrs không thể sao chép) nhưng sẽ minh họa một điểm:

std::vector< scoped_ptr<T> > tPtrVec;
{
     scoped_ptr<T> tPtr(new T());
     tPtrVec.push_back(tPtr);
     // raw T* is freed
}
tPtrVec[0]->DoSomething(); // accessing freed memory

shared_ptr được tính tham chiếu. Mỗi khi một bản sao hoặc bài tập xảy ra, số lượng tham chiếu được tăng lên. Mỗi khi hàm hủy của một cá thể được kích hoạt, số tham chiếu cho T * thô sẽ giảm đi. Khi nó bằng 0, con trỏ được giải phóng.

std::vector< shared_ptr<T> > tPtrVec;
{
     shared_ptr<T> tPtr(new T());
     // This copy to tPtrVec.push_back and ultimately to the vector storage
     // causes the reference count to go from 1->2
     tPtrVec.push_back(tPtr);
     // num references to T goes from 2->1 on the destruction of tPtr
}
tPtrVec[0]->DoSomething(); // raw T* still exists, so this is safe

yếu_ptr là một tham chiếu yếu đến một con trỏ được chia sẻ yêu cầu bạn kiểm tra xem liệu con trỏ được chia sẻ có còn tồn tại không

std::vector< weak_ptr<T> > tPtrVec;
{
     shared_ptr<T> tPtr(new T());
     tPtrVec.push_back(tPtr);
     // num references to T goes from 1->0
}
shared_ptr<T> tPtrAccessed =  tPtrVec[0].lock();
if (tPtrAccessed[0].get() == 0)
{
     cout << "Raw T* was freed, can't access it"
}
else
{
     tPtrVec[0]->DoSomething(); // raw 
}

intrusive_ptr thường được sử dụng khi có ptr thông minh của bên thứ 3 mà bạn phải sử dụng. Nó sẽ gọi một hàm miễn phí để thêm và giảm số tham chiếu. Xem liên kết để tăng tài liệu để biết thêm thông tin.


không if (tPtrAccessed[0].get() == 0)giả sử được if (tPtrAccessed.get() == 0) ?
Rajeshwar

@DougT. Bạn có tin rằng Java sử dụng cùng một ý tưởng với Tài liệu tham khảo không? Mềm, cứng, yếu vv?
gansub

20

Đừng bỏ qua boost::ptr_containertrong bất kỳ cuộc khảo sát về tăng con trỏ thông minh. Chúng có thể là vô giá trong các tình huống mà một ví dụ std::vector<boost::shared_ptr<T> >sẽ quá chậm.


Trên thực tế, lần trước tôi đã thử nó, điểm chuẩn cho thấy khoảng cách hiệu suất đã đóng đáng kể kể từ khi tôi viết bài này, ít nhất là trên PC CT điển hình! Cách tiếp cận ptr_container hiệu quả hơn vẫn có thể có một số lợi thế trong các trường hợp sử dụng thích hợp.
timday

12

Tôi thứ hai lời khuyên về việc xem tài liệu. Nó không đáng sợ như nó có vẻ. Và một vài gợi ý ngắn:

  • scoped_ptr- một con trỏ tự động bị xóa khi đi ra khỏi phạm vi. Lưu ý - không thể chuyển nhượng, nhưng không giới thiệu
  • intrusive_ptr- con trỏ đếm tham chiếu không có chi phí hoạt động smart_ptr. Tuy nhiên, đối tượng tự lưu trữ số tham chiếu
  • weak_ptr- làm việc cùng với shared_ptrđể giải quyết các tình huống dẫn đến sự phụ thuộc vòng tròn (đọc tài liệu và tìm kiếm trên google để có hình ảnh đẹp;)
  • shared_ptr - chung, mạnh nhất (và nặng nhất) của các con trỏ thông minh (từ những con trỏ được cung cấp bởi boost)
  • Cũng có cái cũ auto_ptr, đảm bảo rằng đối tượng mà nó trỏ đến sẽ tự động bị hủy khi điều khiển rời khỏi một phạm vi. Tuy nhiên, nó có ngữ nghĩa sao chép khác với những người còn lại.
  • unique_ptr- sẽ đi kèm với C ++ 0x

Phản hồi để chỉnh sửa:


8
Tôi đến đây vì tôi thấy tài liệu tăng quá đáng sợ.
Francois Botha
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.