ตัวชี้สมาร์ท (เพิ่ม) อธิบาย


220

ความแตกต่างระหว่างพอยน์เตอร์ชุดต่อไปนี้คืออะไร? คุณใช้ตัวชี้แต่ละตัวในรหัสการผลิตเมื่อใด?

ตัวอย่างจะได้รับการชื่นชม!

  1. scoped_ptr

  2. shared_ptr

  3. weak_ptr

  4. intrusive_ptr

คุณใช้บูสต์ในรหัสการผลิตหรือไม่?

คำตอบ:


339

คุณสมบัติพื้นฐานของพอยน์เตอร์อัจฉริยะ

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

  • ไม่มีความเป็นเจ้าของเลย
  • โอนกรรมสิทธิ์
  • ส่วนแบ่งการเป็นเจ้าของ

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

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

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

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

C ++ 1x ให้การสนับสนุนพื้นเมืองสำหรับการถ่ายโอนความเป็นเจ้าของโดยการแนะนำสิ่งที่เรียกว่า "ตัวสร้างการย้าย" และ "ตัวดำเนินการกำหนดค่าการย้าย" unique_ptrนอกจากนี้ยังมาพร้อมกับการดังกล่าวเป็นตัวชี้สมาร์ทการถ่ายโอนการเป็นเจ้าของที่เรียกว่า

การจัดหมวดหมู่ตัวชี้สมาร์ท

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

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

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

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

unique_ptrเป็นการถ่ายโอนของตัวชี้ความเป็นเจ้าของ คุณไม่สามารถคัดลอกได้ แต่คุณสามารถย้ายได้โดยใช้ตัวสร้างการย้ายของ 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!

นี่คือความหมายที่ std :: auto_ptr เชื่อฟัง แต่เนื่องจากขาดการสนับสนุนดั้งเดิมสำหรับการย้ายจึงไม่สามารถให้บริการได้โดยไม่ผิดพลาด unique_ptr จะขโมยทรัพยากรโดยอัตโนมัติจาก unique_ptr อื่นชั่วคราวซึ่งเป็นหนึ่งในคุณสมบัติหลักของซีแมนทิกส์การย้าย auto_ptr จะถูกคัดค้านในการเปิดตัว C ++ Standard ครั้งถัดไปซึ่งเป็นการสนับสนุนของ unique_ptr C ++ 1x จะอนุญาตให้บรรจุวัตถุที่สามารถเคลื่อนย้ายได้ แต่ไม่สามารถคัดลอกลงในคอนเทนเนอร์ได้ ดังนั้นคุณสามารถใส่อะไรที่ไม่เหมือนใครเข้าไปในเวกเตอร์ได้ ฉันจะหยุดที่นี่และอ้างอิงถึงคุณในบทความที่ดีเกี่ยวกับเรื่องนี้ถ้าคุณต้องการอ่านเพิ่มเติมเกี่ยวกับเรื่องนี้


3
ขอบคุณสำหรับการสรรเสริญเพื่อน ฉันขอบคุณมันดังนั้นคุณจะได้รับ +1 ด้วย: p
Johannes Schaub - litb

@litb: ฉันสงสัยใน "การโอนความเป็นเจ้าของ"; ฉันยอมรับว่าไม่มีการถ่ายโอนความเป็นเจ้าของจริงระหว่างวัตถุใน C ++ 03 แต่สำหรับตัวชี้สมาร์ทไม่สามารถทำได้โดยกลไกการคัดลอกแบบทำลายล้างที่ระบุไว้ที่นี่informit.com/articles/article.aspx?p=31529&seqNum= 5 .
ตำนาน 2k

3
คำตอบที่ยอดเยี่ยม หมายเหตุ: auto_ptrเลิกใช้แล้ว (C ++ 11)
nickolay

2
"สิ่งนี้กลับทำลายการใช้งานในคอนเทนเนอร์เนื่องจากข้อกำหนดระบุพฤติกรรมบางอย่างของตัวสร้างสำเนาขององค์ประกอบของคอนเทนเนอร์ซึ่งไม่สอดคล้องกับพฤติกรรมที่เรียกว่า" ตัวสร้างการเคลื่อนย้าย "ของตัวชี้สมาร์ทเหล่านี้" ไม่ได้รับส่วนนั้น
ราชา

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

91

scoped_ptrเป็นวิธีที่ง่ายที่สุด เมื่อมันออกนอกขอบเขตมันจะถูกทำลาย รหัสต่อไปนี้ผิดกฎหมาย (scoped_ptrs ไม่สามารถคัดลอกได้) แต่จะแสดงให้เห็นถึงจุดหนึ่ง:

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ถูกนับการอ้างอิง ทุกครั้งที่มีการคัดลอกหรือการมอบหมายเกิดขึ้นจำนวนการอ้างอิงจะเพิ่มขึ้น ทุกครั้งที่มีการยิง destructor ของอินสแตนซ์จำนวนการอ้างอิงสำหรับ raw T * จะลดลง เมื่อเป็น 0 ตัวชี้จะถูกปล่อยให้เป็นอิสระ

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

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

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เมื่อมีสมาร์ทโฟน ptr ของบุคคลที่สามที่คุณต้องใช้ มันจะเรียกฟังก์ชั่นฟรีเพื่อเพิ่มและลดจำนวนการอ้างอิงดูลิงค์เพื่อเพิ่มเอกสารสำหรับข้อมูลเพิ่มเติม


ไม่if (tPtrAccessed[0].get() == 0)ควรที่จะเป็นif (tPtrAccessed.get() == 0) อย่างไร
Rajeshwar

@DougT คุณเชื่อว่า Java ใช้ความคิดเดียวกันกับการอ้างอิงหรือไม่ อ่อน, แข็ง, อ่อนแอ ฯลฯ ?
gansub

20

อย่ามองข้ามboost::ptr_containerการสำรวจเพื่อเพิ่มพอยน์เตอร์อัจฉริยะ พวกเขาสามารถประเมินค่าได้ในสถานการณ์ที่เช่นstd::vector<boost::shared_ptr<T> >จะช้าเกินไป


ที่จริงแล้วครั้งสุดท้ายที่ฉันลองมันการเปรียบเทียบแสดงให้เห็นว่าช่องว่างของประสิทธิภาพปิดลงอย่างมีนัยสำคัญตั้งแต่ฉันเขียนตอนนี้อย่างน้อยใน HW ทั่วไปของพีซี! วิธีการที่มีประสิทธิภาพมากขึ้นของ ptr_container อาจยังมีข้อได้เปรียบในกรณีการใช้งานเฉพาะ
2559

12

ฉันสองคำแนะนำเกี่ยวกับการดูเอกสาร มันไม่น่ากลัวอย่างที่คิด และคำใบ้สั้น ๆ เล็กน้อย:

  • scoped_ptr- ตัวชี้ถูกลบโดยอัตโนมัติเมื่อไม่อยู่ในขอบเขต หมายเหตุ - ไม่สามารถกำหนดได้ แต่ไม่แนะนำค่าใช้จ่าย
  • intrusive_ptr - ตัวชี้นับการอ้างอิงโดยไม่มีค่าใช้จ่าย smart_ptrตัวชี้อ้างอิงนับด้วยค่าใช้จ่ายไม่ อย่างไรก็ตามวัตถุเองเก็บนับการอ้างอิง
  • weak_ptr - ทำงานร่วมกับ shared_ptrเพื่อจัดการกับสถานการณ์ที่ทำให้เกิดการพึ่งพาแบบวงกลม (อ่านเอกสารประกอบและค้นหาภาพที่ดีใน google)
  • shared_ptr - ตัวชี้สมาร์ทพอยน์เตอร์ทั่วไปและทรงพลังที่สุด (จากรุ่นที่เสนอโดยการเพิ่ม)
  • นอกจากนี้ยังมีเก่าauto_ptrที่ช่วยให้มั่นใจว่าวัตถุที่มันจะถูกทำลายโดยอัตโนมัติเมื่อการควบคุมออกจากขอบเขต อย่างไรก็ตามมันมีความหมายที่แตกต่างจากคนอื่น ๆ
  • unique_ptr- จะมาพร้อมกับ C ++ 0x

การตอบสนองต่อการแก้ไข: ใช่


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