std :: weak_ptr มีประโยชน์เมื่อใด


268

ผมเริ่มศึกษาชี้สมาร์ทของ C ++ 11 std::weak_ptrและฉันไม่เห็นการใช้ประโยชน์ใด มีใครบอกฉันได้บ้างเมื่อstd::weak_ptrมีประโยชน์ / จำเป็น


คำตอบ:


231

ตัวอย่างที่ดีคือแคช

สำหรับวัตถุที่เข้าถึงล่าสุดคุณต้องการเก็บไว้ในหน่วยความจำดังนั้นคุณจึงถือตัวชี้ที่แข็งแกร่งกับพวกเขา คุณสแกนแคชเป็นระยะและตัดสินใจว่าวัตถุใดที่ไม่ได้รับการเข้าถึงล่าสุด คุณไม่จำเป็นต้องเก็บมันไว้ในหน่วยความจำคุณจึงกำจัดตัวชี้ที่แข็งแกร่ง

แต่จะเกิดอะไรขึ้นถ้าวัตถุนั้นถูกใช้งานอยู่และโค้ดอื่น ๆ บางตัวมีตัวชี้ที่แข็งแกร่งอยู่ หากแคชกำจัดตัวชี้ไปยังวัตถุเท่านั้นจะไม่สามารถค้นหาได้อีก ดังนั้นแคชจะเก็บตัวชี้ที่อ่อนแอไปยังวัตถุที่จำเป็นต้องค้นหาหากเกิดขึ้นกับหน่วยความจำ

นี่คือสิ่งที่ตัวชี้ที่อ่อนแอทำ - ช่วยให้คุณสามารถค้นหาวัตถุถ้ามันยังคงอยู่รอบ ๆ แต่ไม่ให้มันไปรอบ ๆ ถ้าไม่มีอะไรต้องการ


8
ดังนั้น std :: wake_ptr สามารถชี้เฉพาะที่ตัวชี้อื่นและชี้ไปที่ nullptr เมื่อวัตถุที่แหลมถูกลบ / ไม่ชี้โดยตัวชี้อื่น ๆ อีกต่อไป?

27
@RM: โดยทั่วไปแล้วใช่ เมื่อคุณมีตัวชี้ที่อ่อนแอคุณสามารถพยายามที่จะเลื่อนไปยังตัวชี้ที่แข็งแกร่ง หากวัตถุนั้นยังคงมีอยู่ (เพราะอย่างน้อยหนึ่งตัวชี้ที่แข็งแกร่งไปยังคงอยู่) การดำเนินการที่ประสบความสำเร็จและให้ตัวชี้ที่แข็งแกร่งกับคุณ หากวัตถุนั้นไม่มีอยู่ (เพราะตัวชี้ที่แข็งแกร่งทั้งหมดหายไป) การดำเนินการนั้นจะล้มเหลว (และโดยทั่วไปแล้วคุณตอบสนองโดยทิ้งตัวชี้จุดอ่อน)
David Schwartz

12
ในขณะที่ตัวชี้ที่แข็งแกร่งช่วยให้วัตถุยังมีชีวิตอยู่อ่อนตัวอ่อนสามารถมองเห็น ... โดยไม่รบกวนกับเวลาชีวิตของวัตถุ
Vivandiere

3
อีกตัวอย่างหนึ่งที่ฉันเคยใช้อย่างน้อยสองสามครั้งคือเมื่อใช้ผู้สังเกตการณ์บางครั้งมันก็สะดวกที่จะให้ผู้ดูแลรักษารายการของตัวชี้ที่อ่อนแอและทำความสะอาดรายการของตัวเอง มันช่วยประหยัดความพยายามเล็กน้อยในการลบผู้สังเกตการณ์อย่างชัดเจนเมื่อถูกลบและคุณไม่จำเป็นต้องมีข้อมูลเกี่ยวกับวัตถุที่มีอยู่เมื่อทำลายผู้สังเกตการณ์ซึ่งโดยทั่วไปจะทำให้สิ่งต่าง ๆ ลดความซับซ้อนลงอย่างมาก
Jason C

3
รอเกิดอะไรขึ้นกับแคชที่ถือ shared_ptr และเพิ่งลบมันออกจากรายการเมื่อมันควรจะถูกล้างออกจากหน่วยความจำ ผู้ใช้ใด ๆ จะถือ shared_ptr เหมือนกันทั้งหมดและทรัพยากรแคชจะถูกล้างทันทีที่ผู้ใช้ทั้งหมดทำ
rubenvb

299

std::weak_ptrเป็นวิธีที่ดีมากที่จะแก้ปัญหาที่ตัวชี้ห้อยปัญหา เพียงแค่ใช้ตัวชี้แบบดิบมันเป็นไปไม่ได้ที่จะรู้ว่าข้อมูลที่อ้างอิงนั้นถูกจัดสรรคืนหรือไม่ แต่โดยปล่อยให้std::shared_ptrการจัดการข้อมูลและการจัดหาstd::weak_ptrกับผู้ใช้งานของข้อมูลที่ผู้ใช้สามารถตรวจสอบความถูกต้องของข้อมูลโดยการเรียกหรือexpired()lock()

คุณไม่สามารถทำสิ่งนี้ได้ด้วยตัวstd::shared_ptrเองเพียงอย่างเดียวเนื่องจากทุกstd::shared_ptrอินสแตนซ์แบ่งปันความเป็นเจ้าของข้อมูลที่ไม่ได้ถูกลบออกก่อนที่อินสแตนซ์ทั้งหมดของstd::shared_ptrจะถูกลบออก นี่คือตัวอย่างของวิธีตรวจสอบตัวชี้ห้อยโดยใช้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
ตกลงมันเป็นถ้าคุณตั้งค่าตัวชี้ (เป็นเจ้าของ) เป็นโมฆะ (ลบหน่วยความจำ) ตัวชี้ (อ่อนแอ) อื่น ๆ ทั้งหมดไปยังหน่วยความจำเดียวกันจะถูกตั้งค่าเป็นโมฆะ
Pat-Laugh

std::weak_ptr::lockสร้างใหม่std::shared_ptrที่แชร์ความเป็นเจ้าของของวัตถุที่มีการจัดการ
นายท่าน Yar

129

คำตอบอื่นหวังว่าง่ายกว่า (สำหรับเพื่อนชาว Google)

สมมติว่าคุณมีTeamและMemberวัตถุ

เห็นได้ชัดว่ามันเป็นความสัมพันธ์ที่: วัตถุจะมีตัวชี้ไปของมันTeam Membersและมีโอกาสที่สมาชิกจะมีตัวชี้หลังไปที่Teamวัตถุของพวกเขา

จากนั้นคุณมีวงจรการพึ่งพา หากคุณใช้shared_ptrวัตถุจะไม่ถูกปลดปล่อยโดยอัตโนมัติอีกต่อไปเมื่อคุณละทิ้งการอ้างอิงกับวัตถุเหล่านั้นเพราะพวกมันอ้างอิงซึ่งกันและกันในลักษณะที่เป็นวงโคจร นี่คือหน่วยความจำรั่ว

weak_ptrคุณทำลายนี้โดยใช้ "เจ้าของ" มักจะใช้shared_ptrและ "เจ้าของ" ใช้weak_ptrไปยังผู้ปกครองของตนและแปลงเป็นชั่วคราวไปshared_ptrเมื่อมันต้องการเข้าถึงผู้ปกครอง

เก็บ PTR ที่อ่อนแอ:

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

จากนั้นใช้เมื่อจำเป็น

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
นี่เป็นหน่วยความจำรั่วอย่างไร หากทีมถูกทำลายมันจะทำลายสมาชิกดังนั้นจำนวนการอ้างอิง shared_ptr จะเป็น 0 และถูกทำลายด้วยเช่นกัน?
paulm

4
@paulm Team จะไม่ทำลายสมาชิก "ของ" ประเด็นทั้งหมดshared_ptrคือการแบ่งปันความเป็นเจ้าของดังนั้นจึงไม่มีใครมีความรับผิดชอบพิเศษในการเพิ่มหน่วยความจำมันจะถูกปลดปล่อยโดยอัตโนมัติเมื่อไม่ได้ใช้งานอีกต่อไป ถ้าไม่มีลูป ... คุณอาจมีหลายทีมที่แชร์ผู้เล่นคนเดียวกัน (ทีมที่ผ่านมา?) หากวัตถุทีม "เป็นเจ้าของ" สมาชิกก็ไม่จำเป็นต้องใช้ a shared_ptrเพื่อเริ่มต้นด้วย
Offirmo

1
มันจะไม่ทำลายพวกเขา แต่ shared_ptr นั้นจะออกไปนอกขอบเขตพร้อมกับลดการใช้ use_count ดังนั้น ณ จุดนี้ use_count คือ 0 และดังนั้น shared_ptr จะลบสิ่งที่ชี้ไป
paulm

2
@ paulm คุณพูดถูก แต่เนื่องจากในตัวอย่างนี้ทีมยังได้รับshared_ptrการอ้างอิงโดย "สมาชิกในทีม" เมื่อใดจะถูกทำลาย? สิ่งที่คุณกำลังอธิบายเป็นกรณีที่ไม่มีการวนซ้ำ
Offirmo

14
ฉันคิดว่ามันไม่เลว หากสมาชิกสามารถเป็นสมาชิกของหลาย ๆ ทีมการใช้การอ้างอิงจะไม่ทำงาน
Mazyod

22

นี่คือตัวอย่างหนึ่งมอบให้กับผมโดย @jleahy: std::shared_ptr<Task>สมมติว่าคุณมีคอลเลกชันของงานดำเนินการถ่ายทอดสดและการจัดการโดย คุณอาจต้องการทำบางสิ่งบางอย่างกับงานเหล่านั้นเป็นระยะดังนั้นเหตุการณ์ตัวจับเวลาอาจเข้าไปstd::vector<std::weak_ptr<Task>>และให้งานบางอย่างทำ อย่างไรก็ตามในเวลาเดียวกันภารกิจอาจตัดสินใจพร้อม ๆ กันว่ามันไม่จำเป็นและจะต้องตายอีกต่อไป ตัวจับเวลาสามารถตรวจสอบว่างานยังมีชีวิตอยู่หรือไม่โดยการใช้ตัวชี้ที่ใช้ร่วมกันจากตัวชี้ที่อ่อนแอและใช้ตัวชี้ที่ใช้ร่วมกันโดยไม่ต้องมีค่าว่าง


4
: ฟังดูเหมือนเป็นตัวอย่างที่ดี แต่คุณช่วยอธิบายเพิ่มเติมเกี่ยวกับตัวอย่างอีกหน่อยได้ไหม? ฉันคิดว่าเมื่องานเสร็จแล้วมันควรถูกลบออกจาก std :: vector <std :: weak_ptr <Task>> โดยไม่มีการตรวจสอบเป็นระยะ ดังนั้นไม่แน่ใจว่า std :: vector <std :: weak_ptr <>> มีประโยชน์มากที่นี่
Gob00st

ความคิดเห็นที่คล้ายกันกับคิว: สมมติว่าคุณมีวัตถุและคุณจัดคิวไว้สำหรับทรัพยากรบางอย่างวัตถุอาจถูกลบในขณะที่รอ ดังนั้นหากคุณเข้าแถวอ่อนแอ _ptrs คุณไม่ต้องกังวลกับการลบรายการออกจากที่นั่น Weak_ptrs จะถูกทำให้เป็นโมฆะและจะถูกยกเลิกเมื่อ encoutnered
zzz777

1
@ zzz777: ตรรกะที่ทำให้วัตถุเป็นโมฆะอาจไม่ได้ตระหนักถึงการมีอยู่ของคิวหรือเวกเตอร์ของผู้สังเกตการณ์ ดังนั้นผู้สังเกตการณ์จึงทำการวนซ้ำแยกกันไปตามพอยน์เตอร์ที่อ่อนแอโดยใช้ตัวที่ยังมีชีวิตอยู่และนำตัวที่ตายแล้วออกจากภาชนะ ...
Kerrek SB

1
@KerekSB: ใช่และในกรณีของคิวคุณไม่จำเป็นต้องวนซ้ำแยกต่างหาก - จากนั้นทรัพยากรจะพร้อมใช้งานคุณละทิ้ง weak_ptrs ที่หมดอายุ (ถ้ามี) จนกว่าคุณจะใช้งานได้ (ถ้ามี)
zzz777

คุณสามารถให้เธรดลบตัวเองออกจากคอลเลกชันได้ แต่นั่นจะสร้างการพึ่งพาและต้องการการล็อก
curiousguy

16

มีประโยชน์กับ Boost.Asio เมื่อคุณไม่รับประกันว่าวัตถุเป้าหมายจะยังคงอยู่เมื่อเรียกใช้ตัวจัดการแบบอะซิงโครนัส เคล็ดลับคือการผูกweak_ptrเข้ากับวัตถุจัดการ asynchonous ใช้std::bindหรือจับแลมบ์ดา

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";
        }
    } );
}

นี่คือตัวแปรของself = shared_from_this()สำนวนที่มักพบในตัวอย่าง Boost.Asio ซึ่งตัวจัดการแบบอะซิงโครนัสที่ค้างอยู่จะไม่ยืดอายุการใช้งานของวัตถุเป้าหมาย แต่ยังคงปลอดภัยหากลบวัตถุเป้าหมาย


เหตุใดจึงต้องใช้เวลานานกว่าจะพบคำตอบนี้ ... PS คุณไม่ได้ใช้การจับภาพของคุณthis
Orwellophile

@Orwellophile คงที่ พลังแห่งนิสัยเมื่อใช้self = shared_from_this()สำนวนเมื่อตัวจัดการเรียกใช้เมธอดภายในคลาสเดียวกัน
Emile Cormier

16

shared_ptr : ถือวัตถุจริง

weak_ptr : ใช้lockเพื่อเชื่อมต่อกับเจ้าของจริงหรือส่งกลับค่า NULL shared_ptrมิฉะนั้น

PTR ที่อ่อนแอ

พูดประมาณweak_ptrบทบาทคล้ายกับบทบาทของหน่วยงานที่อยู่อาศัย หากไม่มีตัวแทนเราจะต้องตรวจสอบบ้านแบบสุ่มในเมือง ตัวแทนทำให้แน่ใจว่าเราเข้าเยี่ยมชมบ้านเหล่านั้นเท่านั้นซึ่งยังคงเข้าถึงและให้เช่า


14

weak_ptrนอกจากนี้ยังเป็นสิ่งที่ดีในการตรวจสอบการลบวัตถุที่ถูกต้อง - โดยเฉพาะในการทดสอบหน่วย กรณีการใช้งานทั่วไปอาจมีลักษณะเช่นนี้:

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

เมื่อใช้พอยน์เตอร์เป็นสิ่งสำคัญที่จะต้องเข้าใจพอยน์เตอร์ประเภทต่าง ๆ ที่มีให้ใช้ พอยน์เตอร์มีสี่ประเภทในสองหมวดดังนี้:

  • พอยน์เตอร์ดิบ:
    • ตัวชี้ดิบ [ie SomeClass* ptrToSomeClass = new SomeClass();]
  • ตัวชี้สมาร์ท:
    • พอยน์เตอร์ที่ไม่ซ้ำกัน [ie
      std::unique_ptr<SomeClass> uniquePtrToSomeClass ( new SomeClass() );
      ]
    • พอยน์เตอร์ที่ใช้ร่วมกัน [ie
      std::shared_ptr<SomeClass> sharedPtrToSomeClass ( new SomeClass() );
      ]
    • ตัวชี้ที่อ่อนแอ [ie
      std::weak_ptr<SomeClass> weakPtrToSomeWeakOrSharedPtr ( weakOrSharedPtr );
      ]

ตัวชี้แบบ Raw (บางครั้งเรียกว่า "ตัวชี้แบบดั้งเดิม" หรือ "ตัวชี้แบบ C") แสดงลักษณะการทำงานของตัวชี้ 'กระดูกเปลือย' และเป็นแหล่งที่มาของข้อบกพร่องและหน่วยความจำรั่ว ตัวชี้แบบ Raw ไม่ได้ให้วิธีการติดตามการเป็นเจ้าของทรัพยากรและนักพัฒนาจะต้องเรียก 'ลบ' ด้วยตนเองเพื่อให้แน่ใจว่าพวกเขาไม่ได้สร้างหน่วยความจำรั่ว สิ่งนี้จะกลายเป็นเรื่องยากหากมีการแชร์ทรัพยากรเพราะอาจเป็นการยากที่จะรู้ว่าวัตถุใด ๆ ที่ยังคงชี้ไปที่ทรัพยากร ด้วยเหตุผลเหล่านี้ควรหลีกเลี่ยงตัวชี้แบบดิบและใช้เฉพาะในส่วนที่สำคัญต่อประสิทธิภาพของรหัสที่มีขอบเขต จำกัด

พอยน์เตอร์ที่ไม่ซ้ำกันเป็นตัวชี้สมาร์ทพื้นฐานที่ 'เป็นเจ้าของ' ตัวชี้ดิบพื้นฐานไปยังทรัพยากรและรับผิดชอบในการโทรลบและเพิ่มหน่วยความจำที่จัดสรรเมื่อวัตถุที่เป็นเจ้าของ 'ตัวชี้ที่ไม่ซ้ำกันออกไปจากขอบเขต ชื่อ 'ที่ไม่ซ้ำกัน' หมายถึงความจริงที่ว่ามีเพียงวัตถุเดียวเท่านั้นที่อาจ 'เป็นเจ้าของ' ตัวชี้ที่ไม่ซ้ำกัน ณ เวลาที่กำหนด ความเป็นเจ้าของอาจถูกถ่ายโอนไปยังวัตถุอื่นผ่านคำสั่งย้าย แต่ตัวชี้ที่ไม่ซ้ำกันจะไม่สามารถคัดลอกหรือแชร์ได้ ด้วยเหตุผลเหล่านี้พอยน์เตอร์ที่ไม่ซ้ำกันจึงเป็นทางเลือกที่ดีสำหรับพอยน์เตอร์ดิบในกรณีที่มีเพียงวัตถุเดียวเท่านั้นที่ต้องการตัวชี้ในเวลาที่กำหนดและสิ่งนี้จะช่วยบรรเทานักพัฒนาจากความต้องการ

พอยน์เตอร์ที่ใช้ร่วมกันเป็นตัวชี้สมาร์ทประเภทอื่นที่คล้ายกับพอยน์เตอร์ที่ไม่ซ้ำกัน แต่อนุญาตให้วัตถุจำนวนมากมีความเป็นเจ้าของพอยน์เตอร์ร่วมกัน เช่นเดียวกับตัวชี้ที่ไม่ซ้ำกันตัวชี้ที่ใช้ร่วมกันมีหน้าที่รับผิดชอบในการเพิ่มหน่วยความจำที่จัดสรรให้เมื่อวัตถุทั้งหมดถูกชี้ไปยังทรัพยากร มันทำได้โดยใช้เทคนิคที่เรียกว่าการนับการอ้างอิง ทุกครั้งที่วัตถุใหม่รับกรรมสิทธิ์ของตัวชี้ที่ใช้ร่วมกันจำนวนการอ้างอิงจะเพิ่มขึ้นทีละหนึ่ง ในทำนองเดียวกันเมื่อวัตถุออกนอกขอบเขตหรือหยุดชี้ไปที่ทรัพยากรการนับการอ้างอิงจะลดลงโดยหนึ่ง เมื่อจำนวนการอ้างอิงถึงศูนย์หน่วยความจำที่จัดสรรจะถูกปล่อยให้เป็นอิสระ ด้วยเหตุผลเหล่านี้พอยน์เตอร์ที่ใช้ร่วมกันเป็นตัวชี้สมาร์ทพอยน์เตอร์ที่มีประสิทธิภาพมากซึ่งควรใช้ทุกครั้งที่ออบเจ็กต์หลายตัวต้องชี้ไปที่ทรัพยากรเดียวกัน

ในที่สุดพอยน์เตอร์ที่อ่อนแอเป็นตัวชี้สมาร์ทประเภทอื่นที่แทนที่จะชี้ไปที่ทรัพยากรโดยตรงพวกเขาชี้ไปที่ตัวชี้อื่น (อ่อนหรือแชร์) ตัวชี้ที่อ่อนแอไม่สามารถเข้าถึงวัตถุได้โดยตรง แต่พวกเขาสามารถบอกได้ว่าวัตถุนั้นยังคงอยู่หรือว่ามันหมดอายุแล้ว ตัวชี้ที่อ่อนแอสามารถแปลงชั่วคราวเป็นตัวชี้ที่ใช้ร่วมกันเพื่อเข้าถึงวัตถุที่ชี้ไปยัง (หากยังคงมีอยู่) เมื่อต้องการแสดงตัวอย่างพิจารณาตัวอย่างต่อไปนี้:

  • คุณไม่ว่างและมีการประชุมที่ทับซ้อนกัน: การประชุม A และการประชุม B
  • คุณตัดสินใจไปที่การประชุม A และเพื่อนร่วมงานของคุณไปที่การประชุม B
  • คุณบอกเพื่อนร่วมงานของคุณว่าถ้าการประชุม B ยังคงดำเนินต่อไปหลังจากการประชุม A สิ้นสุดลงคุณจะเข้าร่วม
  • สถานการณ์สองสถานการณ์ต่อไปนี้สามารถเล่นได้:
    • การประชุม A สิ้นสุดลงและการประชุม B ยังคงดำเนินต่อไปดังนั้นคุณจึงเข้าร่วม
    • การประชุม A สิ้นสุดลงและการประชุม B ก็สิ้นสุดลงดังนั้นคุณจึงไม่สามารถเข้าร่วมได้

ในตัวอย่างคุณมีตัวชี้ที่อ่อนแอต่อ Meeting B คุณไม่ใช่ "เจ้าของ" ในการประชุม B ดังนั้นจึงสามารถจบได้โดยไม่มีคุณและคุณไม่ทราบว่าจะสิ้นสุดหรือไม่เว้นแต่คุณจะตรวจสอบ หากยังไม่สิ้นสุดคุณสามารถเข้าร่วมและมีส่วนร่วมมิฉะนั้นคุณจะไม่สามารถทำได้ สิ่งนี้แตกต่างจากการมีตัวชี้ที่ใช้ร่วมกันในการประชุม B เนื่องจากคุณจะเป็น "เจ้าของ" ทั้งในการประชุม A และการประชุม B (การเข้าร่วมทั้งสองในเวลาเดียวกัน)

ตัวอย่างแสดงให้เห็นว่าตัวชี้จุดอ่อนทำงานอย่างไรและมีประโยชน์เมื่อวัตถุต้องเป็นผู้สังเกตการณ์ภายนอกแต่ไม่ต้องการความรับผิดชอบในการแบ่งปันความเป็นเจ้าของ สิ่งนี้มีประโยชน์อย่างยิ่งในสถานการณ์ที่วัตถุทั้งสองจำเป็นต้องชี้ไปที่กันและกัน (aka การอ้างอิงแบบวงกลม) ด้วยพอยน์เตอร์ที่ใช้ร่วมกันจะไม่สามารถปล่อยวัตถุใด ๆ ได้เนื่องจากวัตถุอื่น ๆ ยังคงชี้ไปที่ เมื่อพอยน์เตอร์ตัวใดตัวหนึ่งเป็นพอยน์เตอร์ตัวอ่อนวัตถุที่ถือตัวชี้แบบอ่อนสามารถยังสามารถเข้าถึงออบเจ็กต์อื่นได้เมื่อจำเป็นโดยที่มันยังคงอยู่


6

นอกเหนือจากกรณีการใช้ที่ถูกต้องอื่น ๆ ที่กล่าวถึงแล้วstd::weak_ptrเป็นเครื่องมือที่ยอดเยี่ยมในสภาพแวดล้อมแบบมัลติเธรดเพราะ

  • มันไม่ได้เป็นเจ้าของวัตถุและไม่สามารถขัดขวางการลบในเธรดอื่น
  • std::shared_ptrร่วมกับstd::weak_ptrปลอดภัยกับตัวชี้ห้อย - ในทางตรงกันข้ามกับstd::unique_ptrร่วมกับพอยน์เตอร์ดิบ
  • std::weak_ptr::lock()เป็นการดำเนินการแบบอะตอมมิก (ดูเพิ่มเติมที่เกี่ยวกับความปลอดภัยของเธรดของ weak_ptr )

พิจารณางานที่จะโหลดภาพทั้งหมดของไดเรกทอรี (~ 10.000) พร้อมกันลงในหน่วยความจำ (เช่นแคชภาพย่อ) เห็นได้ชัดว่าวิธีที่ดีที่สุดในการทำเช่นนี้คือการควบคุมเธรดซึ่งจัดการและจัดการอิมเมจและเธรดผู้ทำงานหลายอันที่โหลดอิมเมจ ตอนนี้เป็นงานง่าย ต่อไปนี้เป็นการใช้งานที่ง่ายมาก ( join()ละเว้น ฯลฯ จะต้องจัดการเธรดต่างกันในการนำไปใช้จริง ฯลฯ )

// 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) );
   }
};

แต่มันมีความซับซ้อนมากขึ้นหากคุณต้องการขัดขวางการโหลดภาพเช่นเนื่องจากผู้ใช้เลือกไดเรกทอรีอื่น หรือแม้ว่าคุณต้องการทำลายผู้จัดการ

คุณต้องการการสื่อสารแบบเธรดและต้องหยุดตัวโหลดเธรดทั้งหมดก่อนที่คุณจะสามารถเปลี่ยนm_imageDatasฟิลด์ได้ ไม่เช่นนั้นโหลดจะทำการโหลดจนกว่าภาพทั้งหมดจะเสร็จสิ้น - แม้ว่าจะล้าสมัยไปแล้วก็ตาม ในตัวอย่างง่าย ๆ ที่จะไม่ยากเกินไป แต่ในสภาพแวดล้อมจริงสิ่งต่าง ๆ อาจมีความซับซ้อนมากขึ้น

เธรดอาจเป็นส่วนหนึ่งของเธรดพูลที่ใช้โดยผู้จัดการหลายคนซึ่งบางคนกำลังหยุดทำงานและบางคนก็ไม่ได้ ฯลฯ พารามิเตอร์ง่าย ๆimagesToLoadจะเป็นคิวที่ถูกล็อคซึ่งผู้จัดการเหล่านั้นจะผลักดันคำขอภาพจากเธรดควบคุมที่แตกต่างกัน กับผู้อ่านโผล่คำขอ - ตามลำดับโดยพลการ - ที่ปลายอีกด้าน ดังนั้นการสื่อสารจึงยากช้าและผิดพลาดง่าย วิธีที่สง่างามมากเพื่อหลีกเลี่ยงการสื่อสารใด ๆ เพิ่มเติมในกรณีดังกล่าวคือการใช้ร่วมกับstd::shared_ptrstd::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) );
   }
};

การนำไปปฏิบัตินี้เกือบจะง่ายเหมือนการใช้ครั้งแรกไม่จำเป็นต้องมีการสื่อสารเพิ่มเติมในเธรดและอาจเป็นส่วนหนึ่งของเธรดพูล / คิวในการนำไปใช้จริง เนื่องจากมีการข้ามอิมเมจที่หมดอายุและอิมเมจที่ไม่หมดอายุจะถูกประมวลผลเธรดที่ไม่จำเป็นต้องถูกหยุดระหว่างการทำงานปกติ คุณสามารถเปลี่ยนเส้นทางหรือทำลายผู้จัดการของคุณได้อย่างปลอดภัยเนื่องจากผู้อ่านตรวจสอบ fn หากตัวชี้การเป็นเจ้าของไม่หมดอายุ


2

http://en.cppreference.com/w/cpp/memory/weak_ptr std :: weak_ptr เป็นตัวชี้สมาร์ทที่มีการอ้างอิงที่ไม่ใช่เจ้าของ ("อ่อนแอ") ไปยังวัตถุที่จัดการโดย std :: shared_ptr มันจะต้องถูกแปลงเป็น std :: shared_ptr เพื่อเข้าถึงวัตถุที่อ้างอิง

std :: weak_ptr แบบจำลองการเป็นเจ้าของชั่วคราว: เมื่อวัตถุต้องการเข้าถึงได้หากมีอยู่แล้วและอาจถูกลบได้ตลอดเวลาโดยบุคคลอื่น std :: weak_ptr ถูกใช้เพื่อติดตามวัตถุและถูกแปลงเป็น std: : shared_ptr เพื่อรับสิทธิ์การเป็นเจ้าของชั่วคราว หาก std :: shared_ptr ดั้งเดิมถูกทำลายในเวลานี้อายุการใช้งานของวัตถุจะถูกขยายจนกระทั่ง std ชั่วคราว :: shared_ptr ถูกทำลายเช่นกัน

นอกจากนี้ std :: weak_ptr ใช้ในการแบ่งการอ้างอิงแบบวงกลมของ std :: shared_ptr


" จะทำลายการอ้างอิงแบบวงกลม " ได้อย่างไร
curiousguy

2

มีข้อเสียเปรียบของตัวชี้ที่ใช้ร่วมกัน: shared_pointer ไม่สามารถจัดการกับการพึ่งพารอบผู้ปกครองเด็ก หมายความว่าคลาสแม่ใช้วัตถุของคลาสเด็กโดยใช้ตัวชี้ที่ใช้ร่วมกันในไฟล์เดียวกันถ้าคลาสเด็กใช้วัตถุของคลาสแม่ ตัวชี้ที่ใช้ร่วมกันจะล้มเหลวในการทำลายวัตถุทั้งหมดแม้ตัวชี้ที่ใช้ร่วมกันจะไม่เรียกตัวทำลายในสถานการณ์อ้างอิงรอบ ตัวชี้ที่ใช้ร่วมกันโดยทั่วไปไม่สนับสนุนกลไกการนับจำนวนการอ้างอิง

ข้อเสียเปรียบนี้เราสามารถเอาชนะโดยใช้ weak_pointer


อ้างอิงอ่อนแอสามารถจัดการกับการอ้างอิงแบบวงกลมได้อย่างไร
curiousguy

1
@crownguy เด็กใช้การอ้างอิงที่อ่อนแอไปยังผู้ปกครองจากนั้นผู้ปกครองสามารถยกเลิกการจัดสรรเมื่อไม่มีการอ้างอิง (แข็งแรง) ที่ใช้ร่วมกันชี้ไปที่มัน ดังนั้นเมื่อเข้าถึงผู้ปกครองผ่านทางเด็กการอ้างอิงที่อ่อนแอจะต้องทดสอบเพื่อดูว่าผู้ปกครองยังคงมีอยู่ อีกทางเลือกหนึ่งเพื่อหลีกเลี่ยงเงื่อนไขพิเศษกลไกการติดตามการอ้างอิงแบบวงกลม (ทั้งการทำเครื่องหมาย - การกวาดหรือการตรวจสอบการลดจำนวน refcount ซึ่งทั้งสองอย่างมีประสิทธิภาพเชิงเส้นกำกับที่ไม่ดี) สามารถทำลายการอ้างอิงที่ใช้ร่วมกันแบบวงกลม อื่น ๆ
Shelby Moore III

@ShelbyMooreIII " ต้องทดสอบเพื่อดูว่าผู้ปกครองยังคงมีอยู่ " ใช่และคุณจะต้องตอบสนองอย่างถูกต้องกับกรณีที่ไม่พร้อมใช้งาน! ซึ่งไม่ได้เกิดขึ้นกับการอ้างอิงจริง (เช่นแรง) ซึ่งหมายถึงการอ้างอิงที่อ่อนแอไม่ได้เป็นการทดแทนที่ลดลง: มันต้องการการเปลี่ยนแปลงในตรรกะ
curiousguy

2
@crossguy คุณไม่ได้ถามว่า "จะweak_ptrจัดการกับการพึ่งพาแบบวงกลมโดยไม่มีการเปลี่ยนแปลงในตรรกะโปรแกรมเป็นแบบหล่นแทนได้shared_ptrอย่างไร" :-)
Shelby Moore III

2

เมื่อเราไม่ต้องการเป็นเจ้าของวัตถุ:

Ex:

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

ในคลาสข้างต้น wPtr1 ไม่ได้เป็นเจ้าของทรัพยากรที่ชี้โดย wPtr1 หากทรัพยากรถูกลบแล้ว wPtr1 จะหมดอายุ

เพื่อหลีกเลี่ยงการพึ่งพาแบบวงกลม:

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

ตอนนี้ถ้าเราสร้าง shared_ptr ของคลาส B และ A แล้ว use_count ของตัวชี้ทั้งสองก็คือสองตัว

เมื่อ shared_ptr ดับขอบเขต od การนับยังคงเป็น 1 และด้วยเหตุนี้วัตถุ A และ B จึงไม่ถูกลบ

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;  
}

เอาท์พุท:

A()
B()

อย่างที่เราเห็นได้จากผลลัพธ์ที่ตัวชี้ A และ B ไม่เคยถูกลบและหน่วยความจำรั่ว

เพื่อหลีกเลี่ยงปัญหาดังกล่าวเพียงแค่ใช้ weak_ptr ในคลาส A แทน shared_ptr ซึ่งเหมาะสมกว่า


2

ฉันเห็นstd::weak_ptr<T>ว่าเป็นตัวจัดการกับstd::shared_ptr<T>: ช่วยให้ฉันได้รับstd::shared_ptr<T>หากยังคงมีอยู่ แต่จะไม่ยืดอายุการใช้งาน มีหลายสถานการณ์เมื่อมุมมองดังกล่าวมีประโยชน์:

// 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.
        }
    }
};

อีกสถานการณ์ที่สำคัญคือการทำลายวงจรในโครงสร้างข้อมูล

// 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 มีการพูดคุยที่ยอดเยี่ยมที่อธิบายการใช้งานคุณสมบัติภาษาที่ดีที่สุด (ในกรณีนี้คือพอยน์เตอร์อัจฉริยะ) เพื่อให้มั่นใจว่าLeak Freedom by Default (ความหมาย: คลิกทุกอย่างในสถานที่โดยการก่อสร้าง; มันเป็นสิ่งที่ต้องดู

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.