Khi nào std :: yếu_ptr hữu ích?


Câu trả lời:


231

Một ví dụ tốt sẽ là một bộ đệm.

Đối với các đối tượng được truy cập gần đây, bạn muốn giữ chúng trong bộ nhớ, vì vậy bạn giữ một con trỏ mạnh cho chúng. Định kỳ, bạn quét bộ đệm và quyết định những đối tượng nào không được truy cập gần đây. Bạn không cần phải giữ những thứ đó trong bộ nhớ, vì vậy bạn sẽ thoát khỏi con trỏ mạnh.

Nhưng nếu đối tượng đó đang được sử dụng và một số mã khác giữ một con trỏ mạnh đến nó thì sao? Nếu bộ đệm được loại bỏ con trỏ duy nhất của nó đến đối tượng, nó không bao giờ có thể tìm lại được. Vì vậy, bộ đệm giữ một con trỏ yếu đến các đối tượng mà nó cần tìm nếu chúng tình cờ ở lại trong bộ nhớ.

Đây chính xác là những gì một con trỏ yếu làm - nó cho phép bạn xác định vị trí một đối tượng nếu nó vẫn ở xung quanh, nhưng không giữ nó xung quanh nếu không có gì khác cần nó.


8
Vì vậy, std :: Wake_ptr chỉ có thể trỏ đến một điểm con trỏ khác và nó trỏ đến nullptr khi đối tượng được chỉ định bị xóa / không được trỏ bởi bất kỳ con trỏ nào khác nữa?

27
@RM: Về cơ bản, có. Khi bạn có một con trỏ yếu, bạn có thể cố gắng thúc đẩy nó thành một con trỏ mạnh. Nếu đối tượng đó vẫn tồn tại (vì ít nhất một con trỏ mạnh đến nó vẫn tồn tại) thì thao tác đó thành công và cung cấp cho bạn một con trỏ mạnh đến nó. Nếu đối tượng đó không tồn tại (vì tất cả các con trỏ mạnh đã biến mất), thì thao tác đó không thành công (và thông thường bạn phản ứng bằng cách vứt bỏ con trỏ yếu).
David Schwartz

12
Trong khi một con trỏ mạnh giữ cho một đối tượng tồn tại, một yếu đuối có thể nhìn vào nó ... mà không làm hỏng thời gian sống của đối tượng.
Vivandiere

3
Một ví dụ khác, mà tôi đã sử dụng ít nhất một vài lần, là khi triển khai các trình quan sát, đôi khi sẽ trở nên thuận tiện khi chủ thể duy trì một danh sách các con trỏ yếu và làm sạch danh sách của riêng nó. Nó tiết kiệm một chút nỗ lực loại bỏ rõ ràng các quan sát viên khi họ bị xóa và đáng kể hơn là bạn không cần phải có thông tin về các đối tượng có sẵn khi phá hủy các quan sát viên thường đơn giản hóa mọi thứ rất nhiều.
Jason C

3
Đợi đã, có gì sai với bộ đệm giữ shared_ptr và chỉ xóa nó khỏi danh sách khi nó sẽ bị xóa khỏi bộ nhớ? Bất kỳ người dùng nào cũng sẽ giữ một shared_ptr như nhau và tài nguyên được lưu trong bộ nhớ cache sẽ bị xóa ngay khi tất cả người dùng hoàn thành nó.
rubenvb

299

std::weak_ptrlà một cách rất tốt để giải quyết vấn đề con trỏ lơ lửng . Bằng cách chỉ sử dụng các con trỏ thô, không thể biết liệu dữ liệu được tham chiếu đã được xử lý hay chưa. Thay vào đó, bằng cách cho phép std::shared_ptrquản lý dữ liệu và cung cấp std::weak_ptrcho người dùng dữ liệu, người dùng có thể kiểm tra tính hợp lệ của dữ liệu bằng cách gọi expired()hoặc lock().

Bạn không thể làm điều này std::shared_ptrmột mình, bởi vì tất cả các std::shared_ptrtrường hợp chia sẻ quyền sở hữu dữ liệu không bị xóa trước khi tất cả các trường hợp std::shared_ptrbị xóa. Dưới đây là một ví dụ về cách kiểm tra con trỏ lơ lửng bằng cách sử dụng lock():

#include <iostream>
#include <memory>

int main()
{
    // OLD, problem with dangling pointer
    // PROBLEM: ref will point to undefined data!

    int* ptr = new int(10);
    int* ref = ptr;
    delete ptr;

    // NEW
    // SOLUTION: check expired() or lock() to determine if pointer is valid

    // empty definition
    std::shared_ptr<int> sptr;

    // takes ownership of pointer
    sptr.reset(new int);
    *sptr = 10;

    // get pointer to data without taking ownership
    std::weak_ptr<int> weak1 = sptr;

    // deletes managed object, acquires new pointer
    sptr.reset(new int);
    *sptr = 5;

    // get pointer to new data without taking ownership
    std::weak_ptr<int> weak2 = sptr;

    // weak1 is expired!
    if(auto tmp = weak1.lock())
        std::cout << *tmp << '\n';
    else
        std::cout << "weak1 is expired\n";

    // weak2 points to new data (5)
    if(auto tmp = weak2.lock())
        std::cout << *tmp << '\n';
    else
        std::cout << "weak2 is expired\n";
}

1
Ok, như thể bạn đặt con trỏ (sở hữu) cục bộ thành null (xóa bộ nhớ), tất cả các con trỏ (yếu) khác vào cùng một bộ nhớ cũng được đặt thành null
Pat-Cười

std::weak_ptr::locktạo ra một cái mới std::shared_ptrmà chia sẻ quyền sở hữu của đối tượng được quản lý.
Sahib Yar

129

Một câu trả lời khác, hy vọng đơn giản hơn. (đối với nhân viên của Google)

Giả sử bạn có TeamMemberđồ vật.

Rõ ràng đó là một mối quan hệ: Teamđối tượng sẽ có con trỏ đến nó Members. Và có khả năng các thành viên cũng sẽ có một con trỏ trở lại Teamđối tượng của họ .

Sau đó, bạn có một chu kỳ phụ thuộc. Nếu bạn sử dụng shared_ptr, các đối tượng sẽ không còn được tự động giải phóng khi bạn từ bỏ tham chiếu trên chúng, bởi vì chúng tham chiếu lẫn nhau theo cách tuần hoàn. Đây là một rò rỉ bộ nhớ.

Bạn phá vỡ điều này bằng cách sử dụng weak_ptr. "Chủ sở hữu" thường sử dụng shared_ptrvà "chủ sở hữu" sử dụng a weak_ptrcho cha mẹ của nó và chuyển đổi tạm thời thành shared_ptrkhi nó cần quyền truy cập vào cha mẹ của nó.

Lưu trữ một ptr yếu:

weak_ptr<Parent> parentWeakPtr_ = parentSharedPtr; // automatic conversion to weak from shared

sau đó sử dụng nó khi cần thiết

shared_ptr<Parent> tempParentSharedPtr = parentWeakPtr_.lock(); // on the stack, from the weak ptr
if( !tempParentSharedPtr ) {
  // yes, it may fail if the parent was freed since we stored weak_ptr
} else {
  // do stuff
}
// tempParentSharedPtr is released when it goes out of scope

1
Làm thế nào đây là một rò rỉ bộ nhớ? Nếu đội bị hủy, nó sẽ hủy các thành viên của nó, do đó, số lượt ref_ptr sẽ là 0 và cũng bị hủy?
paulm

4
@paulm Nhóm sẽ không phá hủy các thành viên "của nó". Toàn bộ quan điểm shared_ptrlà chia sẻ quyền sở hữu, vì vậy không ai có trách nhiệm đặc biệt để giải phóng bộ nhớ, nó được giải phóng tự động khi không còn sử dụng. Trừ khi có một vòng lặp ... Bạn có thể có một vài đội chia sẻ cùng một người chơi (các đội trong quá khứ?). Nếu đối tượng nhóm "sở hữu" các thành viên, thì không cần phải sử dụng shared_ptrđể bắt đầu.
Offirmo

1
Nó sẽ không phá hủy chúng nhưng shared_ptr của nó sẽ vượt ra khỏi phạm vi của nó, làm giảm use_count, do đó tại thời điểm này use_count là 0 và vì vậy shared_ptr sẽ xóa những gì nó trỏ đến?
paulm

2
@paulm Bạn nói đúng. Nhưng vì, trong ví dụ này, nhóm cũng là mộtshared_ptr tham chiếu bởi các "thành viên trong nhóm", khi nào nó sẽ bị phá hủy? Những gì bạn đang mô tả là một trường hợp không có vòng lặp.
Offirmo

14
Nó không tệ lắm, tôi nghĩ thế. Nếu một thành viên có thể thuộc về nhiều đội, sử dụng tài liệu tham khảo sẽ không hoạt động.
Mazyod

22

Đây là một ví dụ, được đưa ra cho tôi bởi @jleahy: Giả sử bạn có một tập hợp các tác vụ, được thực thi không đồng bộ và được quản lý bởi một std::shared_ptr<Task>. Bạn có thể muốn làm một cái gì đó với các nhiệm vụ đó theo định kỳ, vì vậy một sự kiện hẹn giờ có thể đi qua a std::vector<std::weak_ptr<Task>>và đưa ra các nhiệm vụ phải làm. Tuy nhiên, đồng thời một nhiệm vụ có thể đã quyết định đồng thời rằng nó không còn cần thiết và chết. Do đó, bộ định thời có thể kiểm tra xem tác vụ có còn tồn tại hay không bằng cách tạo một con trỏ dùng chung từ con trỏ yếu và sử dụng con trỏ dùng chung đó, miễn là nó không rỗng.


4
: Nghe có vẻ là một ví dụ hay nhưng bạn có thể giải thích rõ hơn về ví dụ của bạn không? Tôi đang suy nghĩ khi một nhiệm vụ kết thúc, nó sẽ bị xóa khỏi std :: vector <std :: yếu_ptr <Nhiệm vụ >> mà không cần kiểm tra định kỳ. Vì vậy, không chắc chắn nếu std :: vector <std :: yếu_ptr <>> rất hữu ích ở đây.
Yêu tinh

Nhận xét tương tự với hàng đợi: giả sử bạn có các đối tượng và bạn xếp hàng chúng cho một số tài nguyên, các đối tượng có thể bị xóa trong khi chờ đợi. Vì vậy, nếu bạn xếp hàng yếu_ptrs, bạn không phải bận tâm đến việc xóa các mục từ hàng đợi đó. Weak_ptrs sẽ bị vô hiệu hóa và sau đó bị loại bỏ khi được mã hóa.
zzz777

1
@ zzz777: Logic làm mất hiệu lực các đối tượng thậm chí có thể không nhận thức được sự tồn tại của hàng đợi hoặc vectơ của người quan sát. Vì vậy, người quan sát thực hiện một vòng lặp riêng biệt trên các con trỏ yếu, tác động lên những con còn sống và loại bỏ những người chết khỏi container ...
Kerrek SB

1
@KerekSB: có và trong trường hợp xếp hàng, bạn thậm chí không phải có một vòng lặp riêng - thì tài nguyên có sẵn, bạn loại bỏ yếu tố đã hết hạn (nếu có) cho đến khi bạn có được một vòng hợp lệ (nếu có).
zzz777

Bạn cũng có thể có các chủ đề tự xóa khỏi bộ sưu tập, nhưng điều đó sẽ tạo ra sự phụ thuộc và yêu cầu khóa.
tò mò

16

Chúng rất hữu ích với Boost.Asio khi bạn không được đảm bảo rằng đối tượng đích vẫn tồn tại khi trình xử lý không đồng bộ được gọi. Bí quyết là liên kết a weak_ptrvào đối tượng xử lý không đồng bộ, sử dụng std::bindhoặc chụp lambda.

void MyClass::startTimer()
{
    std::weak_ptr<MyClass> weak = shared_from_this();
    timer_.async_wait( [weak](const boost::system::error_code& ec)
    {
        auto self = weak.lock();
        if (self)
        {
            self->handleTimeout();
        }
        else
        {
            std::cout << "Target object no longer exists!\n";
        }
    } );
}

Đây là một biến thể của self = shared_from_this()thành ngữ thường thấy trong các ví dụ Boost.Asio, trong đó trình xử lý không đồng bộ đang chờ xử lý sẽ không kéo dài thời gian tồn tại của đối tượng đích, nhưng vẫn an toàn nếu đối tượng đích bị xóa.


Tại sao phải mất quá nhiều thời gian để tìm câu trả lời này ... PS bạn không sử dụng bản chụp của mìnhthis
Orwellophile

@Hoạt động cố định. Buộc thói quen khi sử dụng self = shared_from_this()thành ngữ khi trình xử lý gọi các phương thức trong cùng một lớp.
Cileier Cormier

16

shared_ptr : giữ đối tượng thực sự.

yếu_ptr : sử dụng lockđể kết nối với chủ sở hữu thực hoặc trả về NULL shared_ptrnếu không.

ptr yếu

Nói một cách đơn giản, weak_ptrvai trò tương tự như vai trò của cơ quan nhà ở . Không có đại lý, để có được một ngôi nhà cho thuê, chúng tôi có thể phải kiểm tra các ngôi nhà ngẫu nhiên trong thành phố. Các đại lý đảm bảo rằng chúng tôi chỉ ghé thăm những ngôi nhà vẫn có thể truy cập và cho thuê.


14

weak_ptrcũng tốt để kiểm tra việc xóa chính xác của một đối tượng - đặc biệt là trong các bài kiểm tra đơn vị. Trường hợp sử dụng điển hình có thể trông như thế này:

std::weak_ptr<X> weak_x{ shared_x };
shared_x.reset();
BOOST_CHECK(weak_x.lock());
... //do something that should remove all other copies of shared_x and hence destroy x
BOOST_CHECK(!weak_x.lock());

13

Khi sử dụng con trỏ, điều quan trọng là phải hiểu các loại con trỏ khác nhau có sẵn và khi sử dụng mỗi loại con trỏ có ý nghĩa. Có bốn loại con trỏ trong hai loại như sau:

  • Con trỏ thô:
    • Con trỏ thô [tức là SomeClass* ptrToSomeClass = new SomeClass(); ]
  • Con trỏ thông minh:
    • Con trỏ độc đáo [tức là
      std::unique_ptr<SomeClass> uniquePtrToSomeClass ( new SomeClass() );
      ]
    • Con trỏ chia sẻ [tức là
      std::shared_ptr<SomeClass> sharedPtrToSomeClass ( new SomeClass() );
      ]
    • Con trỏ yếu [tức là
      std::weak_ptr<SomeClass> weakPtrToSomeWeakOrSharedPtr ( weakOrSharedPtr );
      ]

Con trỏ thô (đôi khi được gọi là "con trỏ kế thừa" hoặc "Con trỏ C") cung cấp hành vi con trỏ 'xương trần' và là một nguồn phổ biến của lỗi và rò rỉ bộ nhớ. Con trỏ thô không cung cấp phương tiện để theo dõi quyền sở hữu tài nguyên và nhà phát triển phải gọi 'xóa' theo cách thủ công để đảm bảo chúng không tạo ra rò rỉ bộ nhớ. Điều này trở nên khó khăn nếu tài nguyên được chia sẻ vì có thể khó khăn để biết liệu có bất kỳ đối tượng nào vẫn đang trỏ đến tài nguyên hay không. Vì những lý do này, các con trỏ thô thường nên tránh và chỉ được sử dụng trong các phần quan trọng về hiệu năng của mã với phạm vi giới hạn.

Con trỏ duy nhất là một con trỏ thông minh cơ bản 'sở hữu' con trỏ thô bên dưới tài nguyên và chịu trách nhiệm gọi xóa và giải phóng bộ nhớ được phân bổ một khi đối tượng 'sở hữu' con trỏ duy nhất nằm ngoài phạm vi. Tên 'duy nhất' chỉ thực tế là chỉ một đối tượng có thể 'sở hữu' con trỏ duy nhất tại một thời điểm nhất định. Quyền sở hữu có thể được chuyển sang một đối tượng khác thông qua lệnh di chuyển, nhưng một con trỏ duy nhất không bao giờ có thể được sao chép hoặc chia sẻ. Vì những lý do này, các con trỏ duy nhất là một thay thế tốt cho các con trỏ thô trong trường hợp chỉ có một đối tượng cần con trỏ tại một thời điểm nhất định và điều này làm giảm bớt nhà phát triển khỏi nhu cầu giải phóng bộ nhớ ở cuối vòng đời của đối tượng sở hữu.

Con trỏ dùng chung là một loại con trỏ thông minh tương tự như con trỏ duy nhất, nhưng cho phép nhiều đối tượng có quyền sở hữu đối với con trỏ dùng chung. Giống như con trỏ duy nhất, các con trỏ được chia sẻ có trách nhiệm giải phóng bộ nhớ được phân bổ một khi tất cả các đối tượng được thực hiện trỏ đến tài nguyên. Nó thực hiện điều này với một kỹ thuật gọi là đếm tham chiếu. Mỗi khi một đối tượng mới có quyền sở hữu con trỏ dùng chung, số tham chiếu sẽ tăng lên một. Tương tự, khi một đối tượng đi ra khỏi phạm vi hoặc dừng trỏ đến tài nguyên, số tham chiếu bị giảm đi bởi một đối tượng. Khi số tham chiếu đạt đến 0, bộ nhớ được phân bổ sẽ được giải phóng. Vì những lý do này, con trỏ dùng chung là một loại con trỏ thông minh rất mạnh nên được sử dụng bất cứ lúc nào nhiều đối tượng cần trỏ đến cùng một tài nguyên.

Cuối cùng, con trỏ yếu là một loại con trỏ thông minh khác, thay vì chỉ trực tiếp đến một tài nguyên, chúng trỏ đến một con trỏ khác (yếu hoặc dùng chung). Con trỏ yếu không thể truy cập trực tiếp vào một đối tượng, nhưng chúng có thể cho biết liệu đối tượng đó có còn tồn tại hay đã hết hạn. Một con trỏ yếu có thể được chuyển đổi tạm thời thành một con trỏ dùng chung để truy cập vào đối tượng trỏ tới (miễn là nó vẫn tồn tại). Để minh họa, hãy xem xét ví dụ sau:

  • Bạn đang bận rộn và có các cuộc họp chồng chéo: Cuộc họp A và Cuộc họp B
  • Bạn quyết định đi đến Cuộc họp A và đồng nghiệp của bạn đi đến Cuộc họp B
  • Bạn nói với đồng nghiệp của mình rằng nếu Cuộc họp B vẫn diễn ra sau khi Cuộc họp A kết thúc, bạn sẽ tham gia
  • Hai kịch bản sau đây có thể diễn ra:
    • Cuộc họp A kết thúc và Cuộc họp B vẫn đang diễn ra, vì vậy bạn tham gia
    • Cuộc họp A kết thúc và Cuộc họp B cũng đã kết thúc, vì vậy bạn không thể tham gia

Trong ví dụ này, bạn có một con trỏ yếu cho Cuộc họp B. Bạn không phải là "chủ sở hữu" trong Cuộc họp B để nó có thể kết thúc mà không có bạn và bạn không biết liệu nó có kết thúc hay không trừ khi bạn kiểm tra. Nếu nó chưa kết thúc, bạn có thể tham gia và tham gia, nếu không, bạn không thể. Điều này khác với việc có một con trỏ dùng chung cho Cuộc họp B vì khi đó bạn sẽ là "chủ sở hữu" trong cả Cuộc họp A và Cuộc họp B (tham gia cả hai cùng một lúc).

Ví dụ minh họa cách một con trỏ yếu hoạt động và hữu ích khi một đối tượng cần phải là người quan sát bên ngoài , nhưng không muốn chịu trách nhiệm chia sẻ quyền sở hữu. Điều này đặc biệt hữu ích trong kịch bản mà hai đối tượng cần trỏ vào nhau (còn gọi là tham chiếu vòng tròn). Với các con trỏ được chia sẻ, không đối tượng nào có thể được giải phóng vì chúng vẫn được 'mạnh' chỉ vào đối tượng khác. Khi một trong các con trỏ là một con trỏ yếu, đối tượng giữ con trỏ yếu vẫn có thể truy cập vào đối tượng khác khi cần, miễn là nó vẫn tồn tại.


6

Ngoài các trường hợp sử dụng hợp lệ đã được đề cập khác std::weak_ptrlà một công cụ tuyệt vời trong môi trường đa luồng, bởi vì

  • Nó không sở hữu đối tượng và vì vậy không thể cản trở việc xóa trong một luồng khác
  • std::shared_ptrkết hợp với std::weak_ptran toàn chống lại con trỏ lơ lửng - ngược lại với std::unique_ptrkết hợp với con trỏ thô
  • std::weak_ptr::lock()là một hoạt động nguyên tử (xem thêm Về an toàn luồng của yếu_ptr )

Xem xét một tác vụ để tải tất cả các hình ảnh của một thư mục (~ 10.000) đồng thời vào bộ nhớ (ví dụ như bộ đệm hình thu nhỏ). Rõ ràng cách tốt nhất để làm điều này là một luồng điều khiển, xử lý và quản lý hình ảnh, và nhiều luồng công nhân, tải hình ảnh. Bây giờ đây là một nhiệm vụ dễ dàng. Đây là một triển khai rất đơn giản ( join()vv được bỏ qua, các luồng sẽ phải được xử lý khác nhau trong một triển khai thực sự, v.v.)

// a simplified class to hold the thumbnail and data
struct ImageData {
  std::string path;
  std::unique_ptr<YourFavoriteImageLibData> image;
};

// a simplified reader fn
void read( std::vector<std::shared_ptr<ImageData>> imagesToLoad ) {
   for( auto& imageData : imagesToLoad )
     imageData->image = YourFavoriteImageLib::load( imageData->path );
}

// a simplified manager
class Manager {
   std::vector<std::shared_ptr<ImageData>> m_imageDatas;
   std::vector<std::unique_ptr<std::thread>> m_threads;
public:
   void load( const std::string& folderPath ) {
      std::vector<std::string> imagePaths = readFolder( folderPath );
      m_imageDatas = createImageDatas( imagePaths );
      const unsigned numThreads = std::thread::hardware_concurrency();
      std::vector<std::vector<std::shared_ptr<ImageData>>> splitDatas = 
        splitImageDatas( m_imageDatas, numThreads );
      for( auto& dataRangeToLoad : splitDatas )
        m_threads.push_back( std::make_unique<std::thread>(read, dataRangeToLoad) );
   }
};

Nhưng nó trở nên phức tạp hơn nhiều, nếu bạn muốn làm gián đoạn việc tải hình ảnh, ví dụ vì người dùng đã chọn một thư mục khác. Hoặc thậm chí nếu bạn muốn phá hủy người quản lý.

Bạn cần giao tiếp luồng và phải dừng tất cả các luồng của trình tải, trước khi bạn có thể thay đổi m_imageDatastrường của mình . Nếu không, các trình tải sẽ tiếp tục tải cho đến khi tất cả các hình ảnh được thực hiện - ngay cả khi chúng đã lỗi thời. Trong ví dụ đơn giản, điều đó sẽ không quá khó, nhưng trong một môi trường thực tế, mọi thứ có thể phức tạp hơn nhiều.

Các luồng có thể là một phần của nhóm luồng được sử dụng bởi nhiều người quản lý, trong đó một số bị dừng và một số thì không. Thông số đơn giản imagesToLoadsẽ là một hàng đợi bị khóa, trong đó các trình quản lý đẩy các yêu cầu hình ảnh của họ từ các luồng điều khiển khác nhau với các độc giả xuất hiện các yêu cầu - theo thứ tự tùy ý - ở đầu kia. Và do đó, việc giao tiếp trở nên khó khăn, chậm chạp và dễ bị lỗi. Một cách rất thanh lịch để tránh mọi giao tiếp bổ sung trong những trường hợp như vậy là sử dụng std::shared_ptrkết hợp với std::weak_ptr.

// a simplified reader fn
void read( std::vector<std::weak_ptr<ImageData>> imagesToLoad ) {
   for( auto& imageDataWeak : imagesToLoad ) {
     std::shared_ptr<ImageData> imageData = imageDataWeak.lock();
     if( !imageData )
        continue;
     imageData->image = YourFavoriteImageLib::load( imageData->path );
   }
}

// a simplified manager
class Manager {
   std::vector<std::shared_ptr<ImageData>> m_imageDatas;
   std::vector<std::unique_ptr<std::thread>> m_threads;
public:
   void load( const std::string& folderPath ) {
      std::vector<std::string> imagePaths = readFolder( folderPath );
      m_imageDatas = createImageDatas( imagePaths );
      const unsigned numThreads = std::thread::hardware_concurrency();
      std::vector<std::vector<std::weak_ptr<ImageData>>> splitDatas = 
        splitImageDatasToWeak( m_imageDatas, numThreads );
      for( auto& dataRangeToLoad : splitDatas )
        m_threads.push_back( std::make_unique<std::thread>(read, dataRangeToLoad) );
   }
};

Việc thực hiện này gần như dễ dàng như lần đầu tiên, không cần bất kỳ giao tiếp luồng bổ sung nào và có thể là một phần của nhóm luồng / hàng đợi trong một triển khai thực sự. Vì các hình ảnh hết hạn bị bỏ qua và các hình ảnh không hết hạn được xử lý, các luồng không bao giờ phải dừng lại trong quá trình hoạt động bình thường. Bạn luôn có thể thay đổi đường dẫn hoặc hủy các trình quản lý của mình một cách an toàn, vì trình đọc fn sẽ kiểm tra, nếu con trỏ sở hữu chưa hết hạn.


2

http://en.cppreference.com/w/cpp/memory/weak_ptr std :: yếu_ptr là một con trỏ thông minh chứa tham chiếu không sở hữu ("yếu") đến một đối tượng được quản lý bởi std :: shared_ptr. Nó phải được chuyển đổi thành std :: shared_ptr để truy cập đối tượng được tham chiếu.

std :: yếu_ptr mô hình quyền sở hữu tạm thời: khi một đối tượng chỉ cần được truy cập nếu nó tồn tại và nó có thể bị xóa bất cứ lúc nào bởi người khác, std :: yếu_ptr được sử dụng để theo dõi đối tượng và nó được chuyển đổi thành std: : shared_ptr để nhận quyền sở hữu tạm thời. Nếu std :: shared_ptr ban đầu bị hủy vào thời điểm này, thời gian tồn tại của đối tượng sẽ được kéo dài cho đến khi std :: shared_ptr tạm thời cũng bị phá hủy.

Ngoài ra, std :: yếu_ptr được sử dụng để phá vỡ các tham chiếu vòng tròn của std :: shared_ptr.


" để phá vỡ các tham chiếu tròn " như thế nào?
tò mò

2

Có một nhược điểm của con trỏ dùng chung: shared_pulum không thể xử lý phụ thuộc chu kỳ cha-con. Có nghĩa là nếu lớp cha sử dụng đối tượng của lớp con bằng cách sử dụng một con trỏ dùng chung, trong cùng một tệp nếu lớp con sử dụng đối tượng của lớp cha. Con trỏ dùng chung sẽ không thể phá hủy tất cả các đối tượng, thậm chí con trỏ dùng chung hoàn toàn không gọi hàm hủy trong kịch bản phụ thuộc chu kỳ. về cơ bản con trỏ chia sẻ không hỗ trợ cơ chế đếm tham chiếu.

Hạn chế này chúng ta có thể khắc phục bằng cách sử dụng yếu.


Làm thế nào một tài liệu tham khảo yếu có thể đối phó với một phụ thuộc tròn?
tò mò

1
@cquilguy, một đứa trẻ sử dụng một tài liệu tham khảo yếu cho cha mẹ, sau đó cha mẹ có thể được giải quyết khi không có tài liệu tham khảo được chia sẻ (mạnh) chỉ vào nó. Do đó, khi truy cập cha mẹ qua con, tham chiếu yếu phải kiểm tra xem cha mẹ có còn hay không. Ngoài ra, để tránh điều kiện bổ sung đó, một cơ chế theo dõi tham chiếu vòng tròn (quét đánh dấu hoặc thăm dò các lần giảm số lượng, cả hai đều có hiệu suất tiệm cận xấu) có thể phá vỡ các tham chiếu được chia sẻ vòng tròn khi các tham chiếu được chia sẻ duy nhất cho cha mẹ và con khác
Shelby Moore III

@ShelbyMooreIII " phải kiểm tra xem phụ huynh có còn " có không, và bạn phải có khả năng phản ứng chính xác với trường hợp không khả dụng! Điều đó không xảy ra với một ref thực sự (tức là mạnh). Điều đó có nghĩa là ref yếu không phải là sự thay thế: nó đòi hỏi phải thay đổi logic.
tò mò

2
@cquilguy bạn đã không hỏi về Làm thế nào một weak_ptrthỏa thuận với sự phụ thuộc vòng tròn mà không có sự thay đổi nào trong logic chương trình như là một sự thay thế thả vào cho shared_ptr? :-)
Shelby Moore III

2

Khi chúng ta không muốn sở hữu đối tượng:

Ví dụ:

class A
{
    shared_ptr<int> sPtr1;
    weak_ptr<int> wPtr1;
}

Trong lớp trên, wPtr1 không sở hữu tài nguyên được trỏ bởi wPtr1. Nếu tài nguyên bị xóa thì wPtr1 đã hết hạn.

Để tránh phụ thuộc vòng tròn:

shard_ptr<A> <----| shared_ptr<B> <------
    ^             |          ^          |
    |             |          |          |
    |             |          |          |
    |             |          |          |
    |             |          |          |
class A           |     class B         |
    |             |          |          |
    |             ------------          |
    |                                   |
    -------------------------------------

Bây giờ nếu chúng ta tạo shared_ptr của lớp B và A, thì use_count của cả hai con trỏ là hai.

Khi shared_ptr đi ra ngoài phạm vi od, số lượng vẫn còn 1 và do đó đối tượng A và B không bị xóa.

class B;

class A
{
    shared_ptr<B> sP1; // use weak_ptr instead to avoid CD

public:
    A() {  cout << "A()" << endl; }
    ~A() { cout << "~A()" << endl; }

    void setShared(shared_ptr<B>& p)
    {
        sP1 = p;
    }
};

class B
{
    shared_ptr<A> sP1;

public:
    B() {  cout << "B()" << endl; }
    ~B() { cout << "~B()" << endl; }

    void setShared(shared_ptr<A>& p)
    {
        sP1 = p;
    }
};

int main()
{
    shared_ptr<A> aPtr(new A);
    shared_ptr<B> bPtr(new B);

    aPtr->setShared(bPtr);
    bPtr->setShared(aPtr);

    return 0;  
}

đầu ra:

A()
B()

Như chúng ta có thể thấy từ đầu ra rằng con trỏ A và B không bao giờ bị xóa và do đó bộ nhớ bị rò rỉ.

Để tránh vấn đề như vậy, chỉ cần sử dụng yếu_ptr trong lớp A thay vì shared_ptr sẽ có ý nghĩa hơn.


2

Tôi thấy std::weak_ptr<T>như một tay cầm một std::shared_ptr<T>: Nó cho phép tôi để có được std::shared_ptr<T>nếu nó vẫn còn tồn tại, nhưng nó sẽ không kéo dài tuổi thọ của pin. Có một số tình huống khi quan điểm như vậy là hữu ích:

// Some sort of image; very expensive to create.
std::shared_ptr< Texture > texture;

// A Widget should be able to quickly get a handle to a Texture. On the
// other hand, I don't want to keep Textures around just because a widget
// may need it.

struct Widget {
    std::weak_ptr< Texture > texture_handle;
    void render() {
        if (auto texture = texture_handle.get(); texture) {
            // do stuff with texture. Warning: `texture`
            // is now extending the lifetime because it
            // is a std::shared_ptr< Texture >.
        } else {
            // gracefully degrade; there's no texture.
        }
    }
};

Một kịch bản quan trọng khác là phá vỡ các chu kỳ trong cấu trúc dữ liệu.

// Asking for trouble because a node owns the next node, and the next node owns
// the previous node: memory leak; no destructors automatically called.
struct Node {
    std::shared_ptr< Node > next;
    std::shared_ptr< Node > prev;
};

// Asking for trouble because a parent owns its children and children own their
// parents: memory leak; no destructors automatically called.
struct Node {
    std::shared_ptr< Node > parent;
    std::shared_ptr< Node > left_child;
    std::shared_ptr< Node > right_child;
};

// Better: break dependencies using a std::weak_ptr (but not best way to do it;
// see Herb Sutter's talk).
struct Node {
    std::shared_ptr< Node > next;
    std::weak_ptr< Node > prev;
};

// Better: break dependencies using a std::weak_ptr (but not best way to do it;
// see Herb Sutter's talk).
struct Node {
    std::weak_ptr< Node > parent;
    std::shared_ptr< Node > left_child;
    std::shared_ptr< Node > right_child;
};

Herb Sutter có một bài nói chuyện tuyệt vời giải thích việc sử dụng tốt nhất các tính năng ngôn ngữ (trong trường hợp này là con trỏ thông minh) để đảm bảo Leak Freedom theo mặc định (có nghĩa là: mọi thứ nhấp vào vị trí bằng cách xây dựng; bạn khó có thể làm hỏng nó). Nó là phải xem.

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.