ฉันควรใช้ตัวชี้ประเภทใดเมื่อใด


228

ตกลงดังนั้นครั้งสุดท้ายที่ฉันเขียน C ++ เพื่อหาเลี้ยงชีพ std::auto_ptrคือ std lib ทั้งหมดที่มีอยู่และboost::shared_ptrเป็นความเดือดดาลทั้งหมด ฉันไม่เคยมองไปที่การเพิ่มประเภทตัวชี้อัจฉริยะอื่น ๆ ฉันเข้าใจว่าตอนนี้ C ++ 11 มีการเพิ่มประเภทบางอย่าง แต่ไม่ใช่ทั้งหมด

ดังนั้นใครบางคนจึงมีอัลกอริทึมง่าย ๆ ในการพิจารณาว่าจะใช้ตัวชี้สมาร์ทตัวไหน ควรรวมถึงคำแนะนำเกี่ยวกับพอยน์เตอร์พอยน์เตอร์ (พอยน์เตอร์ดิบเช่นT*) และสมาร์ทพอยน์เตอร์ที่เหลือ (บางอย่างเช่นนี้จะดีมาก)


ดูเพิ่มเติมstd :: auto_ptr to std :: unique_ptr
Martin York

1
ฉันหวังว่าบางคนจะเกิดขึ้นกับผังงานที่มีประโยชน์เช่นผังการเลือก STLนี้
Alok บันทึก

1
@ แอล: โอ้เป็นคนดีจริง ๆ ! ฉันคำถามที่พบบ่อยมัน
sbi

6
@Dupuplicator ไม่ได้ใกล้เคียงกับการซ้ำซ้อน คำถามที่เชื่อมโยงกล่าวว่า "เมื่อฉันควรใช้ชี้สมาร์ท" และคำถามนี้คือ "เมื่อไหร่ที่ผมใช้เหล่านี้ชี้สมาร์ท?" เช่นอันนี้คือการจัดหมวดหมู่การใช้งานที่แตกต่างกันของตัวชี้สมาร์ทมาตรฐาน คำถามที่เชื่อมโยงไม่ได้ทำเช่นนี้ ความแตกต่างนั้นดูเล็ก แต่มันก็ใหญ่
Rapptz

คำตอบ:


183

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

โปรดทราบว่า Boost นอกจากนี้ข้อเสนอซึ่งอาจจะเป็นทางเลือกที่เหมาะสมกับshared_arrayshared_ptr<std::vector<T> const>

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

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

โปรดทราบอีกครั้งว่า Boost มีรุ่นอาเรย์: scoped_arrayซึ่งมาตรฐานแบบครบวงจรโดยต้องการstd::unique_ptr<T[]>ความเชี่ยวชาญเฉพาะบางส่วนที่จะdelete[]เป็นตัวชี้แทนการdeleteอิง (ด้วยdefault_deleter) std::unique_ptr<T[]>นอกจากนี้ยังมีoperator[]แทนและoperator*operator->

โปรดทราบว่าstd::auto_ptrยังอยู่ในมาตรฐาน แต่มันก็เลิก §D.10 [depr.auto.ptr]

แม่แบบคลาสauto_ptrเลิกใช้แล้ว [ หมายเหตุ:เทมเพลตคลาสunique_ptr(20.7.1) มอบวิธีแก้ปัญหาที่ดีกว่า - บันทึกย่อ ]

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

หากคุณต้องการการอ้างอิงที่ไม่ได้เป็นเจ้าของทรัพยากร แต่คุณไม่ทราบว่าทรัพยากรนั้นจะอยู่ได้นานกว่าวัตถุที่อ้างอิงหรือไม่ให้แพ็คทรัพยากรในshared_ptrและใช้ a weak_ptr- คุณสามารถทดสอบได้ว่าผู้ปกครองshared_ptrยังมีชีวิตอยู่ด้วยlockหรือไม่ ส่งคืน a shared_ptrที่ไม่ใช่ค่า null ถ้าทรัพยากรยังคงมีอยู่ หากต้องการทดสอบว่าทรัพยากรใช้งานexpiredไม่ได้หรือไม่ ทั้งสองอาจฟังดูคล้ายกัน แต่มีความแตกต่างกันมากในการเผชิญกับการดำเนินการที่เกิดขึ้นพร้อมกันเนื่องจากexpiredรับประกันได้เฉพาะค่าส่งคืนสำหรับคำสั่งเดียว การทดสอบที่ไร้เดียงสาดูเหมือนจะเป็นเช่นนั้น

if(!wptr.expired())
  something_assuming_the_resource_is_still_alive();

เป็นสภาพการแข่งขันที่มีศักยภาพ


1
ในกรณีที่ไม่มีความเป็นเจ้าของคุณอาจต้องการอ้างอิงถึงพอยน์เตอร์เว้นแต่คุณไม่ต้องการความเป็นเจ้าของและความสามารถในการตั้งค่าใหม่ที่การอ้างอิงจะไม่ถูกตัดออกแม้คุณอาจต้องการพิจารณาการเขียนวัตถุต้นฉบับให้shared_ptrเป็นตัวชี้และไม่ใช่เจ้าของ a weak_ptr...
David Rodríguez - dribeas

2
ฉันไม่ได้หมายถึงการอ้างอิงถึงตัวชี้แต่เป็นการอ้างอิงแทนตัวชี้ หากไม่มีความเป็นเจ้าของยกเว้นว่าคุณต้องการความสามารถในการตั้งค่าใหม่ (หรือความสามารถในการทำให้เป็นโมฆะได้ แต่ความสามารถในการตั้งค่าใหม่จะไม่มีข้อ จำกัด ) คุณสามารถใช้การอ้างอิงธรรมดาแทนการใช้ตัวชี้
David Rodríguez - dribeas

1
@David: อ่าฉันเข้าใจแล้ว :) ใช่การอ้างอิงไม่ดีสำหรับฉันเองก็ชอบพวกเขาเช่นกันในกรณีดังกล่าว ฉันจะเพิ่มพวกเขา
Xeo

1
@Xeo: shared_array<T>เป็นอีกทางเลือกหนึ่งที่shared_ptr<T[]>ไม่ควรทำshared_ptr<vector<T>>: มันไม่สามารถเติบโตได้
R. Martinho Fernandes

1
@GregroyCurrie: นั่นคือ ... สิ่งที่ฉันเขียน? ฉันบอกว่ามันเป็นตัวอย่างของสภาพการแข่งขันที่อาจเกิดขึ้น
Xeo

127

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

โปรดทราบว่าการเป็นเจ้าของในระบบซอฟต์แวร์นั้นแยกจากความเป็นเจ้าของอย่างที่เราคิดนอกระบบ ตัวอย่างเช่นบุคคลอาจ "เป็นเจ้าของ" บ้านของพวกเขา แต่นั่นไม่ได้แปลว่าPersonวัตถุมีการควบคุมอายุการใช้งานของHouseวัตถุ การผสมผสานแนวคิดในโลกแห่งความจริงเหล่านี้เข้ากับแนวคิดซอฟต์แวร์เป็นวิธีที่แน่นอนในการเขียนโปรแกรมด้วยตัวเอง


ถ้าคุณเป็นเจ้าของ std::unique_ptr<T>แต่เพียงผู้เดียวของวัตถุที่ใช้

หากคุณเป็นเจ้าของได้ร่วมกันของวัตถุ ...
- std::shared_ptr<T>หากมีรอบในการเป็นเจ้าของไม่ใช้
- หากมีรอบการกำหนด "ทิศทาง" และการใช้งานstd::shared_ptr<T>ในทิศทางเดียวและstd::weak_ptr<T>ในอื่น ๆ

หากวัตถุเป็นเจ้าของคุณ แต่มีความเป็นไปได้ที่จะไม่มีเจ้าของให้ใช้พอยน์เตอร์ปกติT*(เช่นพอยน์เตอร์พอยน์เตอร์)

ถ้าวัตถุเป็นเจ้าของคุณ (หรือมิฉะนั้นจะได้รับประกันการดำรงอยู่) T&อ้างอิงการใช้งาน


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

ค่าใช้จ่าย:

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

ตัวอย่าง:

struct BinaryTree
{
    Tree* m_parent;
    std::unique_ptr<BinaryTree> m_children[2]; // or use std::array...
};

ทรีไบนารีไม่ได้เป็นเจ้าของพาเรนต์ แต่การมีอยู่ของต้นไม้แสดงถึงการมีอยู่ของพาเรนต์ (หรือnullptrสำหรับรูท) ดังนั้นจึงใช้พอยน์เตอร์ปกติ ต้นไม้ไบนารี (มีความหมายค่า) มีเจ้าของ std::unique_ptrแต่เพียงผู้เดียวของเด็กของตนเพื่อให้ผู้ที่มี

struct ListNode
{
    std::shared_ptr<ListNode> m_next;
    std::weak_ptr<ListNode> m_prev;
};

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


3
สำหรับตัวอย่างของต้นไม้ไบนารีบางคนแนะนำให้ใช้shared_ptr<BinaryTree>สำหรับเด็กและweak_ptr<BinaryTree>สำหรับความสัมพันธ์ของผู้ปกครอง
David Rodríguez - dribeas

@ DavidRodríguez-dribeas: มันขึ้นอยู่กับว่า Tree นั้นมีความหมายตามตัวอักษรหรือไม่ หากผู้คนกำลังอ้างอิงต้นไม้ของคุณจากภายนอกแม้เมื่อต้นไม้ต้นกำเนิดถูกทำลายดังนั้นใช่คำสั่งผสมที่ใช้ร่วมกัน / ตัวชี้แบบอ่อนจะดีที่สุด
ปีเตอร์อเล็กซานเดอร์

หากวัตถุเป็นเจ้าของคุณและรับประกันว่ามีอยู่แล้วทำไมไม่อ้างอิง
Martin York

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

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

19

ใช้unique_ptr<T>ตลอดเวลายกเว้นเมื่อคุณต้องการการนับการอ้างอิงในกรณีที่ใช้shared_ptr<T>(และสำหรับกรณีที่หายากมากweak_ptr<T>เพื่อป้องกันรอบการอ้างอิง) ในเกือบทุกกรณีความเป็นเจ้าของที่ไม่ซ้ำกันที่สามารถถ่ายโอนได้นั้นใช้ได้

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

ตัวชี้อาร์เรย์: unique_ptrมีความเชี่ยวชาญเป็นT[]ที่เรียกโดยอัตโนมัติdelete[]ผลเพื่อให้คุณสามารถทำunique_ptr<int[]> p(new int[42]);เช่น shared_ptrคุณยังคงต้องการตัวคั่นแบบกำหนดเอง แต่คุณไม่จำเป็นต้องใช้ตัวชี้อาร์เรย์แบบแบ่งใช้หรือแบบพิเศษ แน่นอนว่าสิ่งดังกล่าวมักจะดีที่สุดแทนที่ด้วยstd::vectorล่ะค่ะ แต่น่าเสียดายที่shared_ptrไม่ได้ให้ฟังก์ชั่นการเข้าถึงอาร์เรย์ดังนั้นคุณจะยังคงมีการโทรด้วยตนเองget()แต่unique_ptr<T[]>ให้operator[]แทนและoperator* operator->ไม่ว่าในกรณีใดคุณต้องตรวจสอบด้วยตัวเอง สิ่งนี้ทำให้shared_ptrผู้ใช้ใช้งานได้ง่ายขึ้นเล็กน้อยแม้ว่าจะเป็นข้อได้เปรียบทั่วไปและไม่มีการพึ่งพา Boost ทำให้unique_ptrและshared_ptrผู้ชนะอีกครั้ง

ชี้ขอบเขต: ทำไม่เกี่ยวข้องโดยการเช่นเดียวกับunique_ptrauto_ptr

ไม่มีอะไรมากไปกว่านี้แล้ว ใน C ++ 03 โดยไม่มีความหมายการย้ายสถานการณ์นี้ซับซ้อนมาก แต่ใน C ++ 11 คำแนะนำนั้นง่ายมาก

นอกจากนี้ยังคงใช้ตัวชี้สมาร์ทอื่น ๆ เช่นหรือintrusive_ptr interprocess_ptrอย่างไรก็ตามพวกเขาเป็นโพรงมากและไม่จำเป็นอย่างสมบูรณ์ในกรณีทั่วไป


นอกจากนี้ตัวชี้ดิบสำหรับการทำซ้ำ และสำหรับบัฟเฟอร์พารามิเตอร์ขาออกที่ผู้เรียกนั้นเป็นเจ้าของบัฟเฟอร์
Ben Voigt

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

2
std::unique_ptr<T[]>ให้operator[]แทนและoperator* operator->เป็นความจริงที่ว่าคุณยังต้องทำการตรวจสอบตัวเอง
Xeo

8

กรณีที่ใช้เมื่อใดunique_ptr:

  • วิธีการโรงงาน
  • สมาชิกที่เป็นตัวชี้ (รวม pimpl)
  • การจัดเก็บพอยน์เตอร์ใน stl containters (เพื่อหลีกเลี่ยงการเคลื่อนไหว)
  • การใช้วัตถุไดนามิกในท้องถิ่นที่มีขนาดใหญ่

กรณีที่ใช้เมื่อใดshared_ptr:

  • การแชร์วัตถุข้ามเธรด
  • การแบ่งปันวัตถุโดยทั่วไป

กรณีที่ใช้เมื่อใดweak_ptr:

  • แผนที่ขนาดใหญ่ที่ทำหน้าที่เป็นข้อมูลอ้างอิงทั่วไป (เช่นแผนที่ของซ็อกเก็ตที่เปิดอยู่ทั้งหมด)

รู้สึกอิสระที่จะแก้ไขและเพิ่มมากขึ้น


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