ส่ง shared_ptr <Derived> เป็น shared_ptr <Base>


93

วิธีใดที่ดีที่สุดในการส่งผ่านshared_ptrประเภทที่ได้รับไปยังฟังก์ชันที่ใช้shared_ptrประเภทฐาน

โดยทั่วไปฉันส่งผ่านshared_ptrs โดยการอ้างอิงเพื่อหลีกเลี่ยงสำเนาที่ไม่จำเป็น:

int foo(const shared_ptr<bar>& ptr);

แต่จะไม่ได้ผลถ้าฉันพยายามทำสิ่งที่ชอบ

int foo(const shared_ptr<Base>& ptr);

...

shared_ptr<Derived> bar = make_shared<Derived>();
foo(bar);

ฉันสามารถใช้

foo(dynamic_pointer_cast<Base, Derived>(bar));

แต่ดูเหมือนว่าจะไม่เหมาะสมด้วยเหตุผลสองประการ:

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

มีทางออกที่ดีกว่านี้หรือไม่?

อัปเดตสำหรับลูกหลาน:

มันกลายเป็นปัญหาของไฟล์ส่วนหัวที่หายไป นอกจากนี้สิ่งที่ฉันพยายามทำต่อไปนี้ถือเป็นปฏิปักษ์ โดยทั่วไปแล้ว

  • ฟังก์ชั่นที่ไม่ได้ส่งผลกระทบต่อชีวิตของวัตถุ (เช่นวัตถุที่ยังคงถูกต้องสำหรับระยะเวลาของฟังก์ชั่น) int foo(bar& b)ควรจะใช้การอ้างอิงธรรมดาหรือตัวชี้เช่น

  • ฟังก์ชั่นที่ใช้วัตถุ (เช่นเป็นผู้ใช้ขั้นสุดท้ายของวัตถุที่กำหนด) ควรใช้โดยค่าเช่นunique_ptr int foo(unique_ptr<bar> b)ผู้โทรควรstd::moveใส่ค่าลงในฟังก์ชัน

  • ฟังก์ชั่นที่ขยายอายุการใช้งานของวัตถุควรใช้โดยค่าเช่นshared_ptr int foo(shared_ptr<bar> b)คำแนะนำตามปกติเพื่อหลีกเลี่ยงการอ้างอิงแบบวงกลมใช้

ดูรายละเอียดเกี่ยวกับ Back to Basicsของ Herb Sutter


8
ทำไมคุณถึงต้องการที่จะผ่านshared_ptr? ทำไมไม่มีการอ้างอิง const ของบาร์?
ipc

2
dynamicนักแสดงทุกคนจำเป็นสำหรับการดาวน์คาสติ้งเท่านั้น นอกจากนี้การส่งผ่านตัวชี้ที่ได้รับควรใช้งานได้ดี มันจะสร้างใหม่shared_ptrโดยใช้การอ้างอิงเดียวกัน (และเพิ่มขึ้น) และตัวชี้ไปที่ฐานซึ่งจะเชื่อมโยงกับการอ้างอิง const อย่างไรก็ตามเนื่องจากคุณได้รับการอ้างอิงแล้วฉันไม่เห็นว่าทำไมคุณถึงต้องการรับshared_ptrเลย รับBase const&สายและโทรfoo(*bar).
Xeo

@Xeo: การส่งผ่านตัวชี้ที่ได้รับ (เช่นfoo(bar)) ไม่ได้ผลอย่างน้อยใน MSVC 2010
Matt Kline

1
คำว่า "ไม่ได้ผล" หมายความว่าอย่างไร โค้ดคอมไพล์และทำงานอย่างถูกต้อง คุณกำลังถามว่าจะหลีกเลี่ยงการสร้างชั่วคราวshared_ptrเพื่อส่งผ่านไปยังฟังก์ชันได้อย่างไร? ฉันค่อนข้างแน่ใจว่าไม่มีทางหลีกเลี่ยงได้
Mike Seymour

1
@ เซ ธ : ฉันไม่เห็นด้วย ฉันคิดว่ามีเหตุผลที่จะส่งตัวชี้ที่ใช้ร่วมกันตามค่าและมีเหตุผลเพียงเล็กน้อยที่จะส่งผ่านตัวชี้ที่ใช้ร่วมกันโดยการอ้างอิง (และทั้งหมดนี้ไม่มีการสนับสนุนสำเนาที่ไม่จำเป็น) ให้เหตุผลที่นี่stackoverflow.com/questions/10826541/…
R. Martinho Fernandes

คำตอบ:


47

แม้ว่าBaseและDerivedมี covariant และตัวชี้ดิบเพื่อพวกเขาจะทำหน้าที่ตามshared_ptr<Base>และshared_ptr<Derived>มีไม่ covariant วิธีdynamic_pointer_castนี้เป็นวิธีที่ถูกต้องและง่ายที่สุดในการจัดการปัญหานี้

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

แต่ถ้าคุณfoo()ฟังก์ชั่นไม่ได้ต้องการที่จะมีส่วนร่วมในการขยายอายุการใช้งาน (หรือค่อนข้างมีส่วนร่วมในการเป็นเจ้าของร่วมกันของวัตถุ) จากนั้นดีที่สุดที่จะยอมรับconst Base&และ dereference เมื่อผ่านไปshared_ptrfoo()

void foo(const Base& base);
[...]
shared_ptr<Derived> spDerived = getDerived();
foo(*spDerived);

เช่นกันเพราะshared_ptrประเภทไม่สามารถ covariant กฎของการแปลงโดยปริยายในทุกประเภทผลตอบแทน covariant shared_ptr<T>ใช้ไม่ได้เมื่อกลับประเภท


39
พวกเขาไม่ได้เป็นโควาเรีย แต่shared_ptr<Derived>สามารถแปลงเป็นได้โดยปริยายshared_ptr<Base>ดังนั้นโค้ดควรใช้งานได้โดยไม่มีการหล่อแบบเชนานิแกน
Mike Seymour

9
อืมshared_ptr<Ty>มีตัวสร้างที่รับshared_ptr<Other>และทำการแปลงที่เหมาะสมถ้าTy*โดยปริยายแปลงเป็นOther*. และถ้าจำเป็นต้องใช้นักแสดงควรstatic_pointer_castเป็นคนที่เหมาะสมที่นี่ไม่ใช่dynamic_pointer_castหรือ
Pete Becker

จริง แต่ไม่ใช่กับพารามิเตอร์อ้างอิงของเขาเหมือนในคำถาม เขาจะต้องทำสำเนาโดยไม่คำนึงถึง แต่ถ้าเขาใช้ refs to shared_ptrเพื่อหลีกเลี่ยงการนับอ้างอิงก็ไม่มีเหตุผลที่ดีที่จะใช้ a shared_ptrตั้งแต่แรก ใช้const Base&แทนกันจะดีที่สุด
Bret Kuhns

@PeteBecker ดูความคิดเห็นของฉันกับ Mike เกี่ยวกับตัวสร้างการแปลง ฉันไม่รู้จริงๆstatic_pointer_castขอบคุณ
Bret Kuhns

1
@TanveerBadar ไม่แน่ใจ อาจจะล้มเหลวในการรวบรวมในปี 2012? (โดยเฉพาะใช้ Visual Studio 2010 หรือ 2012) แต่คุณพูดถูกจริงๆโค้ดของ OP ควรคอมไพล์อย่างสมบูรณ์หากคอมไพเลอร์มองเห็นคำจำกัดความที่สมบูรณ์ของ a / publicly / class
Bret Kuhns

32

สิ่งนี้จะเกิดขึ้นหากคุณลืมระบุมรดกสาธารณะในคลาสที่ได้รับเช่นถ้าชอบฉันคุณเขียนสิ่งนี้:

class Derived : Base
{
};

classใช้สำหรับพารามิเตอร์เทมเพลต structมีไว้สำหรับกำหนดคลาส (มากที่สุด 45% เป็นเรื่องตลก)
Davis Herring

สิ่งนี้ควรได้รับการพิจารณาอย่างแน่นอนไม่จำเป็นต้องมีนักแสดงเนื่องจากเป็นเพียงสาธารณะเท่านั้นที่หายไป
Alexis Paques

12

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

ที่กล่าวว่ามีสองวิธีในการทำสิ่งนี้ที่ฉันสามารถคิดจากด้านบนของหัวของฉัน:

foo(shared_ptr<Base>(bar));
foo(static_pointer_cast<Base>(bar));

9
ไม่ถูกคัดลอกไม่ถูกควรส่งต่อโดยอ้างอิงทุกครั้งที่ทำได้
Seth Carnegie

6
@SethCarnegie - Herb สร้างโปรไฟล์รหัสของคุณเพื่อดูว่าการส่งผ่านมูลค่าเป็นปัญหาคอขวดหรือไม่?
Pete Becker

25
@SethCarnegie - นั่นไม่ได้ตอบคำถามที่ฉันถาม และสำหรับสิ่งที่คุ้มค่าฉันเขียนการshared_ptrใช้งานที่ Microsoft จัดส่งให้
Pete Becker

6
@SethCarnegie - คุณมีฮิวริสติกถอยหลัง การเพิ่มประสิทธิภาพมือควรโดยทั่วไปไม่สามารถทำได้เว้นแต่คุณสามารถแสดงให้เห็นว่าพวกเขามีความจำเป็น
Pete Becker

21
เป็นเพียงการเพิ่มประสิทธิภาพ "ก่อนกำหนด" หากคุณจำเป็นต้องดำเนินการ ฉันไม่เห็นปัญหาในการนำสำนวนที่มีประสิทธิภาพมาใช้กับสำนวนที่ไม่มีประสิทธิภาพไม่ว่าจะสร้างความแตกต่างในบริบทเฉพาะหรือไม่ก็ตาม
Mark Ransom

11

ตรวจสอบด้วยว่าไฟล์ #includeไฟล์ส่วนหัวที่มีการประกาศแบบเต็มของคลาสที่ได้รับอยู่ในไฟล์ต้นฉบับของคุณ

ฉันมีปัญหานี้ จะไม่โยนไปstd::shared<derived> std::shared<base>ฉันได้ประกาศไปข้างหน้าทั้งสองคลาสเพื่อที่ฉันจะได้ถือพอยน์เตอร์ให้กับพวกเขา แต่เนื่องจากฉันไม่มี#includeคอมไพเลอร์จึงไม่เห็นว่าคลาสหนึ่งมาจากคลาสอื่น


1
ว้าวฉันไม่ได้คาดหวัง แต่สิ่งนี้จะแก้ไขให้ฉัน ฉันระมัดระวังเป็นพิเศษและรวมเฉพาะไฟล์ส่วนหัวที่ฉันต้องการดังนั้นบางไฟล์จึงอยู่ในไฟล์ต้นฉบับเท่านั้นและส่งต่อประกาศในส่วนหัวตามที่คุณกล่าว
jigglypuff

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