std :: shared_ptr เป็นทางเลือกสุดท้าย?


59

ผมก็แค่ดู "Going พื้นเมือง 2012" std::shared_ptrลำธารและผมสังเกตเห็นการอภิปรายเกี่ยวกับ ฉันรู้สึกประหลาดใจเล็กน้อยที่ได้ยินมุมมองเชิงลบของ Bjarne std::shared_ptrและความคิดเห็นของเขาว่าควรใช้เป็น "ทางเลือกสุดท้าย" เมื่ออายุการใช้งานของวัตถุไม่แน่นอน (ซึ่งฉันเชื่อตามที่เขาพูด

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


8
ไม่ได้ใช้พอยน์เตอร์? มีเจ้าของวัตถุที่แตกต่างกันที่จัดการอายุการใช้งานหรือไม่
Bo Persson

2
ข้อมูลที่แชร์อย่างชัดเจนคืออะไร มันยากที่จะไม่ใช้พอยน์เตอร์ std :: shared_pointer จะทำให้สกปรก "จัดการตลอดชีวิต" ในกรณีนั้น
Kamil Klimek

6
คุณคิดว่าการฟังคำแนะนำที่เสนอน้อยลงและมีการโต้แย้งมากกว่าคำแนะนำนั้นหรือไม่? เขาอธิบายถึงระบบที่ใช้คำแนะนำแบบนี้ได้ดี
Nicol Bolas

@ NicolBolas: ฉันฟังคำแนะนำและข้อโต้แย้ง แต่เห็นได้ชัดว่าฉันไม่รู้สึกว่าฉันเข้าใจดีพอ
ronag

เขาพูดว่า "สุดท้าย" ในเวลาใด รับชมบิตที่ 36 นาที ( channel9.msdn.com/Events/GoingNative/GoingNative-2012/ ...... ) เขาบอกว่าเขาระวังการใช้พอยน์เตอร์ แต่เขาหมายถึงพอยน์เตอร์ทั่วไปไม่ใช่แค่ shared_ptr และ unique_ptr แต่ยัง ' ตัวชี้ 'ปกติ' เขาบอกเป็นนัยว่าควรใช้วัตถุ (และไม่ควรเป็นตัวชี้ไปยังวัตถุที่จัดสรรใหม่) คุณคิดบ้างไหมในการนำเสนอในภายหลัง?
Pharap

คำตอบ:


55

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

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


5
+1 สำหรับการเขียนว่าปัญหาไม่ใช่เทคโนเอง (ความเป็นเจ้าของร่วม) แต่ความยากลำบากที่แนะนำสำหรับเราเป็นเพียงมนุษย์ที่ต้องถอดรหัสสิ่งที่เกิดขึ้น
Matthieu M.

อย่างไรก็ตามการใช้วิธีการดังกล่าวจะจำกัดความสามารถของโปรแกรมเมอร์ในการใช้รูปแบบการเขียนโปรแกรมพร้อมกันในคลาส OOP ที่ไม่น่ารำคาญที่สุด (เนื่องจากไม่สามารถคัดลอกได้) ปัญหานี้เกิดขึ้นใน "Going Native 2013"
rwong

48

โลกที่ Bjarne อาศัยอยู่นั้นเป็น ... วิชาการเพื่อความต้องการในระยะที่ดีกว่า หากรหัสของคุณสามารถออกแบบและวางโครงสร้างให้วัตถุมีความสัมพันธ์เชิงลำดับชั้นสูงมากเช่นความสัมพันธ์ของการเป็นเจ้าของนั้นเข้มงวดและไม่ยอมให้รหัสไหลในทิศทางเดียว (จากระดับสูงถึงระดับต่ำ) และวัตถุจะพูดกับสิ่งที่ต่ำกว่า shared_ptrลำดับชั้นแล้วคุณจะไม่พบความจำเป็นมากสำหรับการ มันเป็นสิ่งที่คุณใช้ในโอกาสที่หายากเหล่านั้นซึ่งบางคนต้องฝ่าฝืนกฎ แต่มิฉะนั้นคุณสามารถติดทุกอย่างในvectors หรือโครงสร้างข้อมูลอื่น ๆ ที่ใช้ semantics ค่าและunique_ptrs สำหรับสิ่งที่คุณต้องจัดสรรโดยลำพัง

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

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

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

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

วิธีที่ปลอดภัยเพื่อความอยู่รอดเมื่อสภาวะของโลกไม่แน่นอน

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

ตอนนี้ที่ไม่ได้บอกว่าshared_ptrไม่สามารถจะตื้อ ; มันสามารถ โดยเฉพาะอย่างยิ่งก่อนunique_ptrมีหลายกรณีที่ฉันเพิ่งใช้boost::shared_ptrเพราะฉันต้องผ่านตัวชี้ RAII รอบ ๆ หรือใส่ไว้ในรายการ โดยไม่ต้องย้ายความหมายและunique_ptr, boost::shared_ptrเป็นทางออกที่แท้จริงเท่านั้น

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


4
+1: ดูเช่นที่ boost :: asio ฉันคิดว่าความคิดนี้ครอบคลุมไปถึงหลาย ๆ พื้นที่คุณอาจไม่รู้เวลารวบรวมว่าวิดเจ็ต UI หรือการโทรแบบอะซิงโครนัสเป็นคนสุดท้ายที่ปล่อยวัตถุออกมาและด้วย shared_ptr คุณไม่จำเป็นต้องรู้ เห็นได้ชัดว่ามันใช้ไม่ได้กับทุกสถานการณ์เครื่องมืออีกอัน (มีประโยชน์มาก) ในกล่องเครื่องมือ
Guy Sirton

3
ความเห็นช้าไปหน่อย shared_ptrเหมาะสำหรับระบบที่ c ++ รวมเข้ากับภาษาสคริปต์เช่น python การใช้boost::pythonการนับการอ้างอิงบนด้าน c ++ และ python ให้ความร่วมมืออย่างมาก วัตถุใด ๆ จาก c ++ สามารถเก็บไว้ในหลามและจะไม่ตาย
eudoxos

1
เพียงสำหรับการอ้างอิงความเข้าใจของฉันจะไม่ WebKit shared_ptrมิได้ใช้โครเมี่ยม intrusive_ptrทั้งสองใช้การใช้งานของตัวเอง ฉันนำสิ่งนั้นมาเพราะพวกเขาเป็นทั้งโลกแห่งความจริงของแอพพลิเคชั่นขนาดใหญ่ที่เขียนใน C ++
gman

1
@gman: ฉันพบว่าความคิดเห็นของคุณทำให้เข้าใจผิดมากเนื่องจากการคัดค้านของ Stroustrup ที่จะshared_ptrนำไปใช้อย่างเท่าเทียมกันintrusive_ptr: เขาคัดค้านแนวคิดทั้งหมดของการเป็นเจ้าของร่วมกันไม่ใช่การสะกดคำที่เฉพาะเจาะจง ดังนั้นสำหรับวัตถุประสงค์ของคำถามนี้ผู้ที่มีสองตัวอย่างจริงของโลกของการใช้งานขนาดใหญ่ที่ทำshared_ptrใช้ (และสิ่งที่เพิ่มเติมที่พวกเขาแสดงให้เห็นว่าshared_ptrจะเป็นประโยชน์แม้จะไม่เปิดใช้งานweak_ptr.)
ruakh

1
FWIW เพื่อตอบโต้ข้อเรียกร้องที่ Bjarne อาศัยอยู่ในโลกการศึกษา: ในอาชีพทางอุตสาหกรรมของฉัน (ซึ่งรวมถึงการเป็นผู้ร่วมแลกเปลี่ยน G20 และผู้สร้าง 500G ผู้เล่น MOK) ฉันได้เห็นเพียง 3 กรณีเมื่อเราต้องการจริงๆ เจ้าของร่วมกัน. ฉันเป็น 200% กับ Bjarne ที่นี่
No-Bugs แฮร์

37

std::shared_ptrผมไม่เชื่อว่าฉันเคยใช้

ส่วนใหญ่แล้ววัตถุจะเชื่อมโยงกับคอลเลกชันบางอย่างซึ่งเป็นของตลอดอายุการใช้งาน ในกรณีที่คุณสามารถใช้whatever_collection<o_type>หรือwhatever_collection<std::unique_ptr<o_type>>ว่าการสะสมนั้นเป็นสมาชิกของวัตถุหรือตัวแปรอัตโนมัติ แน่นอนถ้าคุณไม่ต้องการจำนวนวัตถุแบบไดนามิกคุณสามารถใช้อาร์เรย์ขนาดคงที่อัตโนมัติได้

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


Nicol Bolas ให้ความเห็นว่า "หากวัตถุบางอย่างจับตัวชี้เปล่าและวัตถุนั้นตาย ... อุ๊ปส์" และ "วัตถุต้องตรวจสอบให้แน่ใจว่าวัตถุนั้นมีชีวิตตลอดชีวิตของวัตถุนั้นshared_ptrสามารถทำได้"

ฉันไม่ซื้อข้อโต้แย้งนั้น อย่างน้อยก็ไม่ได้shared_ptrแก้ปัญหานี้ เกี่ยวกับ:

  • หากตารางแฮชบางส่วนจับวัตถุและแฮชโค้ดของวัตถุนั้นเปลี่ยนแปลง ... อุ๊ปส์
  • ถ้าฟังก์ชั่นบางตัววนซ้ำเวกเตอร์และองค์ประกอบถูกแทรกลงในเวกเตอร์นั้น ... โอ๊ะโอ

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

วัตถุไม่ได้ "ตาย" บางส่วนของรหัสทำลายพวกเขา และโยนshared_ptrปัญหาแทนที่จะคิดออกสัญญาการโทรเป็นความปลอดภัยที่ผิดพลาด


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

4
@ BenVoigt: แน่นอนความยากลำบากในการผ่านตัวชี้เปล่า ๆ คือคุณไม่รู้อายุการใช้งานของวัตถุ หากวัตถุบางอย่างจับตัวชี้เปล่าและวัตถุนั้นตาย ... อุ๊ปส์ นั่นเป็นสิ่งที่ถูกต้องshared_ptrและweak_ptrถูกออกแบบมาเพื่อหลีกเลี่ยง Bjarne พยายามที่จะมีชีวิตอยู่ในโลกใบนี้ทุกสิ่งล้วนมีช่วงชีวิตที่ดีชัดเจนและทุกสิ่งล้วนถูกสร้างขึ้น และถ้าคุณสามารถสร้างโลกใบนี้ได้ แต่นั่นไม่ใช่ในโลกแห่งความเป็นจริง วัตถุต้องแน่ใจว่าวัตถุนั้นมีชีวิตตลอดชีวิตของวัตถุนั้น เพียง แต่shared_ptrสามารถทำเช่นนั้น
Nicol Bolas

5
@ NicolBolas: นั่นคือความปลอดภัยที่ผิดพลาด หากผู้เรียกฟังก์ชั่นไม่ได้ให้การรับประกันตามปกติ: "วัตถุนี้จะไม่ถูกสัมผัสโดยบุคคลภายนอกใด ๆ ในระหว่างการเรียกใช้ฟังก์ชั่น" ดังนั้นทั้งคู่จึงจำเป็นต้องเห็นด้วยกับชนิดของการดัดแปลงภายนอกที่ได้รับอนุญาต shared_ptrลดการแก้ไขภายนอกที่เฉพาะเจาะจงเพียงอย่างเดียวเท่านั้นและไม่ใช่แม้แต่การแก้ไขที่พบบ่อยที่สุด และมันไม่ใช่ความรับผิดชอบของวัตถุที่จะทำให้แน่ใจว่าอายุการใช้งานของมันนั้นถูกต้องหากสัญญาการเรียกใช้ฟังก์ชันระบุเป็นอย่างอื่น
Ben Voigt

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

6
@Nicol: หากมันกำลังมองหาตัวชี้ในคอลเลกชันบางอย่างมันอาจจะต้องใช้ประเภทตัวชี้ใด ๆ ที่อยู่ในคอลเลกชันนั้นหรือตัวชี้แบบดิบหากคอลเลกชันเก็บค่าไว้ ถ้ามันคือการสร้างวัตถุและโทรต้องการก็ยังควรกลับshared_ptr unique_ptrการแปลงจากunique_ptrจะshared_ptrเป็นเรื่องง่าย แต่กลับเป็นไปไม่ได้ในเชิงตรรกะ
Ben Voigt

16

ฉันไม่ต้องการคิดในแง่ที่แน่นอน (เช่น "วิธีสุดท้าย") แต่เกี่ยวข้องกับโดเมนปัญหา

C ++ สามารถเสนอวิธีการที่หลากหลายในการจัดการอายุการใช้งาน บางคนพยายามทำให้วัตถุอยู่ในสภาพที่เป็นกองซ้อน บางคนพยายามหลีกเลี่ยงข้อ จำกัด นี้ บางคนเป็น "ตัวอักษร" บางคนมีการประมาณ

ที่จริงคุณสามารถ:

  1. ใช้ความหมายค่าบริสุทธิ์ ใช้งานได้กับวัตถุที่ค่อนข้างเล็กซึ่งสิ่งที่สำคัญคือ "ค่า" และไม่ใช่ "ตัวตน" ซึ่งคุณสามารถสันนิษฐานได้ว่าPersonมีสองคนที่เหมือนกันnameเป็นบุคคลเดียวกัน(ดีกว่า: เป็นตัวแทนของบุคคลเดียวกันสองคน ) อายุการใช้งานจะได้รับจากสแต็คเครื่องปลาย -essentially- เรื่อง deosn't ในการเขียนโปรแกรม (ตั้งแต่คนคือมันเป็นชื่อที่ไม่ว่าสิ่งที่Personจะแบกมัน)
  2. ใช้วัตถุที่จัดสรรสแต็กและการอ้างอิงหรือพอยน์เตอร์ที่เกี่ยวข้อง: อนุญาตให้มีความหลากหลายและให้อายุการใช้งานของวัตถุ ไม่จำเป็นต้องใช้ "ตัวชี้อัจฉริยะ" เนื่องจากคุณมั่นใจได้ว่าไม่มีวัตถุใดที่สามารถ "ชี้" โดยโครงสร้างที่ปล่อยให้อยู่ในสแต็กนานกว่าวัตถุที่พวกเขาชี้ไปที่ (สร้างวัตถุขึ้นมาก่อน
  3. ใช้วัตถุที่ได้รับการจัดสรรกองการจัดการกอง : นี่คือสิ่งที่ std :: vector และภาชนะทั้งหมดทำและวัดstd::unique_ptrทำ (คุณสามารถคิดว่ามันเป็นเวกเตอร์ที่มีขนาด 1) อีกครั้งคุณยอมรับวัตถุเริ่มมีอยู่ (และสิ้นสุดการดำรงอยู่ของพวกเขา) ก่อน (หลัง) โครงสร้างข้อมูลที่พวกเขาอ้างถึง

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

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

C ++ isteslf ไม่มีกลไกดั้งเดิมในการตรวจสอบเหตุการณ์นั้น ( while(are_they_needed)) ดังนั้นคุณต้องประมาณด้วย:

  1. ใช้ความเป็นเจ้าของร่วม : อายุการใช้งานของวัตถุถูกผูกไว้กับ "ตัวนับอ้างอิง": ทำงานได้ถ้า "ความเป็นเจ้าของ" สามารถจัดลำดับชั้นได้ นี่คือสิ่งที่ std :: shared_ptr ทำ และ weak_ptr สามารถใช้เพื่อแบ่งลูป วิธีนี้ใช้เวลาส่วนใหญ่ แต่ล้มเหลวในการออกแบบขนาดใหญ่ที่นักออกแบบจำนวนมากทำงานในทีมที่แตกต่างกันและไม่มีเหตุผลที่ชัดเจน (สิ่งที่มาจากความต้องการค่อนข้าง) เกี่ยวกับผู้ที่เป็นเจ้าของสิ่งที่อับ (ตัวอย่างทั่วไปคือ ก่อนหน้าเนื่องจากการอ้างอิงถัดไปก่อนหน้าหรือถัดไปเป็นเจ้าของการอ้างอิงก่อนหน้าถัดไปหรือไม่ใน absebce ของความต้องการโซลูชัน tho จะเท่ากันและในโครงการขนาดใหญ่คุณมีความเสี่ยงที่จะรวมกัน)
  2. ใช้กองเก็บขยะ : คุณไม่สนใจการแข่งขันตลอดชีวิต คุณเรียกใช้ตัวสะสมเป็นครั้งคราวและสิ่งที่ unreachabe ถูกพิจารณาว่า "ไม่ต้องการอีกต่อไป" และ ... ดี ... อะแฮ่ม ... ถูกทำลาย? สรุป? ?. แช่แข็ง มีนักสะสม GC จำนวนมาก แต่ฉันไม่เคยพบคนที่รู้ภาษา C ++ จริงๆ ส่วนใหญ่ของพวกเขาเพิ่มหน่วยความจำไม่สนใจเกี่ยวกับการทำลายวัตถุ
  3. ใช้ตัวรวบรวมข้อมูลขยะ C ++ ที่มีอินเตอร์เฟสมาตรฐานวิธีการที่เหมาะสม ขอให้โชคดีที่ได้พบมัน

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

ตัวรวบรวมขยะมีค่าใช้จ่าย shared_ptr มีค่าน้อยลง Unique_ptr ยิ่งน้อยลงและวัตถุที่จัดการสแต็กมีน้อยมาก

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


9

สิ่งหนึ่งที่เอ่ยโดย Herb Sutter ในเซสชั่นภายหลังคือทุกครั้งที่คุณคัดลอกจะshared_ptr<>มีการเพิ่ม / ลดลง interlocked ที่จะเกิดขึ้น บนโค้ดแบบมัลติเธรดบนระบบมัลติคอร์การซิงโครไนซ์หน่วยความจำไม่สำคัญ เมื่อกำหนดตัวเลือกจะเป็นการดีกว่าหากใช้ค่าสแต็กหรือ a unique_ptr<>และส่งผ่านการอ้างอิงหรือพอยน์เตอร์พอยน์เตอร์


1
หรือผ่านshared_ptrการอ้างอิง lvalue หรือ rvalue ...
ronag

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

สำหรับฉันนี่เป็นรายละเอียดที่สำคัญที่สุด ดูการเพิ่มประสิทธิภาพก่อนวัยอันควร ในกรณีส่วนใหญ่สิ่งนี้ไม่ควรผลักดันการตัดสินใจ
Guy Sirton

1
@gbjbaanb: ใช่พวกเขาอยู่ในระดับ cpu แต่ในระบบมัลติคอร์คุณจะยกเลิกการใช้งานแคชและบังคับใช้หน่วยความจำอุปสรรค
อุปราคา

4
ในโครงการเกมฉันทำงานกับเราพบว่าความแตกต่างของประสิทธิภาพมีความสำคัญมากจนถึงจุดที่เราต้องการตัวชี้นับที่แตกต่างกัน 2 แบบซึ่งเป็น threadsafe ซึ่งไม่มี
Kylotan

7

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

สิ่งหนึ่งที่พวกเขาเห็นพ้องต้องกันก็คือพวกเรา (นักพัฒนาผู้แต่งหนังสือ ฯลฯ ) ทั้งหมดอยู่ใน "ระยะการเรียนรู้" ของ C ++ 11 และมีการกำหนดรูปแบบและสไตล์

ยกตัวอย่างเช่น Herb อธิบายเราควรคาดหวังว่าหนังสือรุ่นใหม่ของ C ++ บางเล่มเช่นEffective C ++ (Meyers) และC ++ มาตรฐานการเข้ารหัส (Sutter & Alexandrescu) สองสามปีในขณะที่ประสบการณ์ของอุตสาหกรรมและแนวปฏิบัติที่ดีที่สุดกับ C ++ 11 กะทะ


5

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

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

วัตถุ A มีตัวชี้ที่ใช้ร่วมกันไปยังวัตถุ A วัตถุ B อื่นสร้าง A1 และ A2 และกำหนด a1.otherA = a2; และ a2.otherA = a1; ตอนนี้ตัวชี้ที่ใช้ร่วมกันของวัตถุ B ที่ใช้สร้าง a1, a2 ออกไปนอกขอบเขต (พูดที่ท้ายฟังก์ชั่น) ตอนนี้คุณมีการรั่วไหล - ไม่มีใครอ้างถึง a1 และ a2 แต่พวกเขาอ้างถึงกันและกันดังนั้นจำนวนการอ้างอิงของพวกเขาจึงเป็น 1 เสมอและคุณรั่วไหล

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

เพื่อสรุป: ฉันคิดว่าความคิดเห็นอ้างอิงโดย OP ต้มลงไปนี้:

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

แก้ไข: แม้ว่านั่นหมายถึง "ไม่ทราบฉันต้องใช้ shared_ptr" คุณยังคงคิดอยู่และตั้งใจทำเช่นนั้น


3

ฉันจะตอบจากประสบการณ์ของฉันกับ Objective-C ซึ่งเป็นภาษาที่มีการอ้างอิงและนับจำนวนวัตถุทั้งหมดในฮีป เนื่องจากมีวิธีหนึ่งในการจัดการวัตถุสิ่งต่าง ๆ จึงง่ายขึ้นสำหรับโปรแกรมเมอร์ ที่ได้รับอนุญาตสำหรับกฎมาตรฐานที่จะกำหนดว่าเมื่อปฏิบัติตามรับประกันความแข็งแกร่งของรหัสและไม่มีการรั่วไหลของหน่วยความจำ นอกจากนี้ยังเป็นไปได้ที่การเพิ่มประสิทธิภาพคอมไพเลอร์ที่ชาญฉลาดจะเกิดขึ้นเช่น ARC ล่าสุด (การนับการอ้างอิงอัตโนมัติ)

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


1

ฉันจะพยายามตอบคำถาม:

เราจะเขียนโปรแกรมโดยไม่ใช้ std :: shared_ptr และจัดการอายุการใช้งานของวัตถุอย่างปลอดภัยได้อย่างไร?

C ++ มีหลายวิธีในการทำหน่วยความจำเช่น:

  1. ใช้struct A { MyStruct s1,s2; };แทน shared_ptr ในขอบเขตของคลาส สิ่งนี้มีไว้สำหรับโปรแกรมเมอร์ขั้นสูงเนื่องจากต้องการให้คุณเข้าใจว่าการทำงานของการพึ่งพาและต้องการความสามารถในการควบคุมการพึ่งพามากพอที่จะ จำกัด การพึ่งพาต้นไม้ ลำดับของคลาสในไฟล์ส่วนหัวเป็นสิ่งสำคัญของสิ่งนี้ ดูเหมือนว่าการใช้งานนี้มีอยู่ทั่วไปกับชนิด native buildin c ++ แต่การใช้งานกับคลาสที่โปรแกรมเมอร์กำหนดไว้ดูเหมือนว่าจะใช้น้อยลงเนื่องจากการพึ่งพาและลำดับของปัญหาในชั้นเรียนเหล่านี้ วิธีนี้ยังมีปัญหากับขนาดของ โปรแกรมเมอร์เห็นปัญหาในเรื่องนี้เป็นข้อกำหนดในการใช้การประกาศล่วงหน้าหรือ #includes ที่ไม่จำเป็นและทำให้โปรแกรมเมอร์จำนวนมากกลับไปใช้วิธีแก้ปัญหาที่ต่ำกว่าของพอยน์เตอร์และในภายหลังเพื่อ shared_ptr
  2. ใช้MyClass &find_obj(int i);โคลน + () shared_ptr<MyClass> create_obj(int i);แทน โปรแกรมเมอร์หลายคนต้องการสร้างโรงงานเพื่อสร้างวัตถุใหม่ shared_ptr เหมาะสำหรับการใช้งานประเภทนี้ ปัญหาคือว่ามันถือว่าโซลูชั่นการจัดการหน่วยความจำที่ซับซ้อนโดยใช้การจัดสรรร้านค้าฮีป / ฟรีแทนสแต็คง่ายขึ้นหรือการแก้ปัญหาตามวัตถุ ลำดับชั้นของคลาส C ++ ที่ดีรองรับโครงร่างการจัดการหน่วยความจำทั้งหมดไม่ใช่แค่หนึ่งในนั้น โซลูชันอ้างอิงที่อ้างอิงสามารถใช้งานได้หากวัตถุที่ส่งคืนถูกเก็บไว้ภายในวัตถุที่มีอยู่แทนที่จะใช้ตัวแปรขอบเขตของฟังก์ชันในเครื่อง ควรหลีกเลี่ยงการส่งผ่านความเป็นเจ้าของจากโรงงานไปยังรหัสผู้ใช้ การคัดลอกวัตถุหลังจากใช้ find_obj () เป็นวิธีที่ดีในการจัดการมัน - ตัวสร้างสำเนาปกติและตัวสร้างแบบปกติ (ของคลาสอื่น) ด้วยพารามิเตอร์ refrerence หรือ clone () สำหรับวัตถุ polymorphic สามารถจัดการได้
  3. ใช้การอ้างอิงแทนตัวชี้หรือ shared_ptrs ทุกคลาส c ++ มีคอนสตรัคเตอร์และสมาชิกข้อมูลอ้างอิงแต่ละคนจะต้องเริ่มต้น การใช้งานนี้สามารถหลีกเลี่ยงการใช้พอยน์เตอร์และ shared_ptrs จำนวนมาก คุณเพียงแค่ต้องเลือกว่าหน่วยความจำของคุณอยู่ในวัตถุหรือภายนอกและเลือกโซลูชัน struct หรือโซลูชันอ้างอิงตามการตัดสินใจ ปัญหาเกี่ยวกับการแก้ปัญหานี้มักจะเกี่ยวข้องกับการหลีกเลี่ยงพารามิเตอร์คอนสตรัคเตอร์ซึ่งเป็นเรื่องปกติ แต่การปฏิบัติที่เป็นปัญหาและเข้าใจผิดว่าควรออกแบบอินเตอร์เฟสสำหรับคลาส

"ควรหลีกเลี่ยงการส่งผ่านความเป็นเจ้าของจากโรงงานไปยังรหัสผู้ใช้" และจะเกิดอะไรขึ้นเมื่อมันเป็นไปไม่ได้? "การใช้การอ้างอิงแทนตัวชี้หรือ shared_ptrs" อืมไม่. ตัวชี้สามารถส่งต่อได้อีกครั้ง การอ้างอิงไม่สามารถ สิ่งนี้บังคับให้มีการ จำกัด เวลาในการก่อสร้างในสิ่งที่เก็บไว้ในคลาส นั่นไม่จริงสำหรับหลาย ๆ สิ่ง วิธีแก้ปัญหาของคุณดูเหมือนจะเข้มงวดและไม่ยืดหยุ่นกับความต้องการของอินเทอร์เฟซที่ใช้ของเหลวมากขึ้นและรูปแบบการใช้งาน
Nicol Bolas

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

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

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

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