ผมเริ่มศึกษาชี้สมาร์ทของ C ++ 11 std::weak_ptr
และฉันไม่เห็นการใช้ประโยชน์ใด มีใครบอกฉันได้บ้างเมื่อstd::weak_ptr
มีประโยชน์ / จำเป็น
ผมเริ่มศึกษาชี้สมาร์ทของ C ++ 11 std::weak_ptr
และฉันไม่เห็นการใช้ประโยชน์ใด มีใครบอกฉันได้บ้างเมื่อstd::weak_ptr
มีประโยชน์ / จำเป็น
คำตอบ:
ตัวอย่างที่ดีคือแคช
สำหรับวัตถุที่เข้าถึงล่าสุดคุณต้องการเก็บไว้ในหน่วยความจำดังนั้นคุณจึงถือตัวชี้ที่แข็งแกร่งกับพวกเขา คุณสแกนแคชเป็นระยะและตัดสินใจว่าวัตถุใดที่ไม่ได้รับการเข้าถึงล่าสุด คุณไม่จำเป็นต้องเก็บมันไว้ในหน่วยความจำคุณจึงกำจัดตัวชี้ที่แข็งแกร่ง
แต่จะเกิดอะไรขึ้นถ้าวัตถุนั้นถูกใช้งานอยู่และโค้ดอื่น ๆ บางตัวมีตัวชี้ที่แข็งแกร่งอยู่ หากแคชกำจัดตัวชี้ไปยังวัตถุเท่านั้นจะไม่สามารถค้นหาได้อีก ดังนั้นแคชจะเก็บตัวชี้ที่อ่อนแอไปยังวัตถุที่จำเป็นต้องค้นหาหากเกิดขึ้นกับหน่วยความจำ
นี่คือสิ่งที่ตัวชี้ที่อ่อนแอทำ - ช่วยให้คุณสามารถค้นหาวัตถุถ้ามันยังคงอยู่รอบ ๆ แต่ไม่ให้มันไปรอบ ๆ ถ้าไม่มีอะไรต้องการ
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";
}
std::weak_ptr::lock
สร้างใหม่std::shared_ptr
ที่แชร์ความเป็นเจ้าของของวัตถุที่มีการจัดการ
คำตอบอื่นหวังว่าง่ายกว่า (สำหรับเพื่อนชาว 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
shared_ptr
คือการแบ่งปันความเป็นเจ้าของดังนั้นจึงไม่มีใครมีความรับผิดชอบพิเศษในการเพิ่มหน่วยความจำมันจะถูกปลดปล่อยโดยอัตโนมัติเมื่อไม่ได้ใช้งานอีกต่อไป ถ้าไม่มีลูป ... คุณอาจมีหลายทีมที่แชร์ผู้เล่นคนเดียวกัน (ทีมที่ผ่านมา?) หากวัตถุทีม "เป็นเจ้าของ" สมาชิกก็ไม่จำเป็นต้องใช้ a shared_ptr
เพื่อเริ่มต้นด้วย
shared_ptr
การอ้างอิงโดย "สมาชิกในทีม" เมื่อใดจะถูกทำลาย? สิ่งที่คุณกำลังอธิบายเป็นกรณีที่ไม่มีการวนซ้ำ
นี่คือตัวอย่างหนึ่งมอบให้กับผมโดย @jleahy: std::shared_ptr<Task>
สมมติว่าคุณมีคอลเลกชันของงานดำเนินการถ่ายทอดสดและการจัดการโดย คุณอาจต้องการทำบางสิ่งบางอย่างกับงานเหล่านั้นเป็นระยะดังนั้นเหตุการณ์ตัวจับเวลาอาจเข้าไปstd::vector<std::weak_ptr<Task>>
และให้งานบางอย่างทำ อย่างไรก็ตามในเวลาเดียวกันภารกิจอาจตัดสินใจพร้อม ๆ กันว่ามันไม่จำเป็นและจะต้องตายอีกต่อไป ตัวจับเวลาสามารถตรวจสอบว่างานยังมีชีวิตอยู่หรือไม่โดยการใช้ตัวชี้ที่ใช้ร่วมกันจากตัวชี้ที่อ่อนแอและใช้ตัวชี้ที่ใช้ร่วมกันโดยไม่ต้องมีค่าว่าง
มีประโยชน์กับ 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 ซึ่งตัวจัดการแบบอะซิงโครนัสที่ค้างอยู่จะไม่ยืดอายุการใช้งานของวัตถุเป้าหมาย แต่ยังคงปลอดภัยหากลบวัตถุเป้าหมาย
this
self = shared_from_this()
สำนวนเมื่อตัวจัดการเรียกใช้เมธอดภายในคลาสเดียวกัน
shared_ptr : ถือวัตถุจริง
weak_ptr : ใช้lock
เพื่อเชื่อมต่อกับเจ้าของจริงหรือส่งกลับค่า NULL shared_ptr
มิฉะนั้น
พูดประมาณweak_ptr
บทบาทคล้ายกับบทบาทของหน่วยงานที่อยู่อาศัย หากไม่มีตัวแทนเราจะต้องตรวจสอบบ้านแบบสุ่มในเมือง ตัวแทนทำให้แน่ใจว่าเราเข้าเยี่ยมชมบ้านเหล่านั้นเท่านั้นซึ่งยังคงเข้าถึงและให้เช่า
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());
เมื่อใช้พอยน์เตอร์เป็นสิ่งสำคัญที่จะต้องเข้าใจพอยน์เตอร์ประเภทต่าง ๆ ที่มีให้ใช้ พอยน์เตอร์มีสี่ประเภทในสองหมวดดังนี้:
SomeClass* ptrToSomeClass = new SomeClass();
]std::unique_ptr<SomeClass> uniquePtrToSomeClass ( new SomeClass() );
std::shared_ptr<SomeClass> sharedPtrToSomeClass ( new SomeClass() );
std::weak_ptr<SomeClass> weakPtrToSomeWeakOrSharedPtr ( weakOrSharedPtr );
ตัวชี้แบบ Raw (บางครั้งเรียกว่า "ตัวชี้แบบดั้งเดิม" หรือ "ตัวชี้แบบ C") แสดงลักษณะการทำงานของตัวชี้ 'กระดูกเปลือย' และเป็นแหล่งที่มาของข้อบกพร่องและหน่วยความจำรั่ว ตัวชี้แบบ Raw ไม่ได้ให้วิธีการติดตามการเป็นเจ้าของทรัพยากรและนักพัฒนาจะต้องเรียก 'ลบ' ด้วยตนเองเพื่อให้แน่ใจว่าพวกเขาไม่ได้สร้างหน่วยความจำรั่ว สิ่งนี้จะกลายเป็นเรื่องยากหากมีการแชร์ทรัพยากรเพราะอาจเป็นการยากที่จะรู้ว่าวัตถุใด ๆ ที่ยังคงชี้ไปที่ทรัพยากร ด้วยเหตุผลเหล่านี้ควรหลีกเลี่ยงตัวชี้แบบดิบและใช้เฉพาะในส่วนที่สำคัญต่อประสิทธิภาพของรหัสที่มีขอบเขต จำกัด
พอยน์เตอร์ที่ไม่ซ้ำกันเป็นตัวชี้สมาร์ทพื้นฐานที่ 'เป็นเจ้าของ' ตัวชี้ดิบพื้นฐานไปยังทรัพยากรและรับผิดชอบในการโทรลบและเพิ่มหน่วยความจำที่จัดสรรเมื่อวัตถุที่เป็นเจ้าของ 'ตัวชี้ที่ไม่ซ้ำกันออกไปจากขอบเขต ชื่อ 'ที่ไม่ซ้ำกัน' หมายถึงความจริงที่ว่ามีเพียงวัตถุเดียวเท่านั้นที่อาจ 'เป็นเจ้าของ' ตัวชี้ที่ไม่ซ้ำกัน ณ เวลาที่กำหนด ความเป็นเจ้าของอาจถูกถ่ายโอนไปยังวัตถุอื่นผ่านคำสั่งย้าย แต่ตัวชี้ที่ไม่ซ้ำกันจะไม่สามารถคัดลอกหรือแชร์ได้ ด้วยเหตุผลเหล่านี้พอยน์เตอร์ที่ไม่ซ้ำกันจึงเป็นทางเลือกที่ดีสำหรับพอยน์เตอร์ดิบในกรณีที่มีเพียงวัตถุเดียวเท่านั้นที่ต้องการตัวชี้ในเวลาที่กำหนดและสิ่งนี้จะช่วยบรรเทานักพัฒนาจากความต้องการ
พอยน์เตอร์ที่ใช้ร่วมกันเป็นตัวชี้สมาร์ทประเภทอื่นที่คล้ายกับพอยน์เตอร์ที่ไม่ซ้ำกัน แต่อนุญาตให้วัตถุจำนวนมากมีความเป็นเจ้าของพอยน์เตอร์ร่วมกัน เช่นเดียวกับตัวชี้ที่ไม่ซ้ำกันตัวชี้ที่ใช้ร่วมกันมีหน้าที่รับผิดชอบในการเพิ่มหน่วยความจำที่จัดสรรให้เมื่อวัตถุทั้งหมดถูกชี้ไปยังทรัพยากร มันทำได้โดยใช้เทคนิคที่เรียกว่าการนับการอ้างอิง ทุกครั้งที่วัตถุใหม่รับกรรมสิทธิ์ของตัวชี้ที่ใช้ร่วมกันจำนวนการอ้างอิงจะเพิ่มขึ้นทีละหนึ่ง ในทำนองเดียวกันเมื่อวัตถุออกนอกขอบเขตหรือหยุดชี้ไปที่ทรัพยากรการนับการอ้างอิงจะลดลงโดยหนึ่ง เมื่อจำนวนการอ้างอิงถึงศูนย์หน่วยความจำที่จัดสรรจะถูกปล่อยให้เป็นอิสระ ด้วยเหตุผลเหล่านี้พอยน์เตอร์ที่ใช้ร่วมกันเป็นตัวชี้สมาร์ทพอยน์เตอร์ที่มีประสิทธิภาพมากซึ่งควรใช้ทุกครั้งที่ออบเจ็กต์หลายตัวต้องชี้ไปที่ทรัพยากรเดียวกัน
ในที่สุดพอยน์เตอร์ที่อ่อนแอเป็นตัวชี้สมาร์ทประเภทอื่นที่แทนที่จะชี้ไปที่ทรัพยากรโดยตรงพวกเขาชี้ไปที่ตัวชี้อื่น (อ่อนหรือแชร์) ตัวชี้ที่อ่อนแอไม่สามารถเข้าถึงวัตถุได้โดยตรง แต่พวกเขาสามารถบอกได้ว่าวัตถุนั้นยังคงอยู่หรือว่ามันหมดอายุแล้ว ตัวชี้ที่อ่อนแอสามารถแปลงชั่วคราวเป็นตัวชี้ที่ใช้ร่วมกันเพื่อเข้าถึงวัตถุที่ชี้ไปยัง (หากยังคงมีอยู่) เมื่อต้องการแสดงตัวอย่างพิจารณาตัวอย่างต่อไปนี้:
ในตัวอย่างคุณมีตัวชี้ที่อ่อนแอต่อ Meeting B คุณไม่ใช่ "เจ้าของ" ในการประชุม B ดังนั้นจึงสามารถจบได้โดยไม่มีคุณและคุณไม่ทราบว่าจะสิ้นสุดหรือไม่เว้นแต่คุณจะตรวจสอบ หากยังไม่สิ้นสุดคุณสามารถเข้าร่วมและมีส่วนร่วมมิฉะนั้นคุณจะไม่สามารถทำได้ สิ่งนี้แตกต่างจากการมีตัวชี้ที่ใช้ร่วมกันในการประชุม B เนื่องจากคุณจะเป็น "เจ้าของ" ทั้งในการประชุม A และการประชุม B (การเข้าร่วมทั้งสองในเวลาเดียวกัน)
ตัวอย่างแสดงให้เห็นว่าตัวชี้จุดอ่อนทำงานอย่างไรและมีประโยชน์เมื่อวัตถุต้องเป็นผู้สังเกตการณ์ภายนอกแต่ไม่ต้องการความรับผิดชอบในการแบ่งปันความเป็นเจ้าของ สิ่งนี้มีประโยชน์อย่างยิ่งในสถานการณ์ที่วัตถุทั้งสองจำเป็นต้องชี้ไปที่กันและกัน (aka การอ้างอิงแบบวงกลม) ด้วยพอยน์เตอร์ที่ใช้ร่วมกันจะไม่สามารถปล่อยวัตถุใด ๆ ได้เนื่องจากวัตถุอื่น ๆ ยังคงชี้ไปที่ เมื่อพอยน์เตอร์ตัวใดตัวหนึ่งเป็นพอยน์เตอร์ตัวอ่อนวัตถุที่ถือตัวชี้แบบอ่อนสามารถยังสามารถเข้าถึงออบเจ็กต์อื่นได้เมื่อจำเป็นโดยที่มันยังคงอยู่
นอกเหนือจากกรณีการใช้ที่ถูกต้องอื่น ๆ ที่กล่าวถึงแล้ว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_ptr
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) );
}
};
การนำไปปฏิบัตินี้เกือบจะง่ายเหมือนการใช้ครั้งแรกไม่จำเป็นต้องมีการสื่อสารเพิ่มเติมในเธรดและอาจเป็นส่วนหนึ่งของเธรดพูล / คิวในการนำไปใช้จริง เนื่องจากมีการข้ามอิมเมจที่หมดอายุและอิมเมจที่ไม่หมดอายุจะถูกประมวลผลเธรดที่ไม่จำเป็นต้องถูกหยุดระหว่างการทำงานปกติ คุณสามารถเปลี่ยนเส้นทางหรือทำลายผู้จัดการของคุณได้อย่างปลอดภัยเนื่องจากผู้อ่านตรวจสอบ fn หากตัวชี้การเป็นเจ้าของไม่หมดอายุ
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
มีข้อเสียเปรียบของตัวชี้ที่ใช้ร่วมกัน: shared_pointer ไม่สามารถจัดการกับการพึ่งพารอบผู้ปกครองเด็ก หมายความว่าคลาสแม่ใช้วัตถุของคลาสเด็กโดยใช้ตัวชี้ที่ใช้ร่วมกันในไฟล์เดียวกันถ้าคลาสเด็กใช้วัตถุของคลาสแม่ ตัวชี้ที่ใช้ร่วมกันจะล้มเหลวในการทำลายวัตถุทั้งหมดแม้ตัวชี้ที่ใช้ร่วมกันจะไม่เรียกตัวทำลายในสถานการณ์อ้างอิงรอบ ตัวชี้ที่ใช้ร่วมกันโดยทั่วไปไม่สนับสนุนกลไกการนับจำนวนการอ้างอิง
ข้อเสียเปรียบนี้เราสามารถเอาชนะโดยใช้ weak_pointer
weak_ptr
จัดการกับการพึ่งพาแบบวงกลมโดยไม่มีการเปลี่ยนแปลงในตรรกะโปรแกรมเป็นแบบหล่นแทนได้shared_ptr
อย่างไร" :-)
เมื่อเราไม่ต้องการเป็นเจ้าของวัตถุ:
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 ซึ่งเหมาะสมกว่า
ฉันเห็น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 (ความหมาย: คลิกทุกอย่างในสถานที่โดยการก่อสร้าง; มันเป็นสิ่งที่ต้องดู