การส่งพอยน์เตอร์ที่ใช้ร่วมกันเป็นอาร์กิวเมนต์


89

ถ้าฉันประกาศว่าวัตถุที่อยู่ในตัวชี้ที่ใช้ร่วมกัน:

std::shared_ptr<myClass> myClassObject(new myClass());

จากนั้นฉันต้องการส่งต่อเป็นอาร์กิวเมนต์ให้กับวิธีการ:

DoSomething(myClassObject);

//the called method
void DoSomething(std::shared_ptr<myClass> arg1)
{
   arg1->someField = 4;
}

ข้างต้นเพียงแค่เพิ่มจำนวนการอ้างอิงของ shared_pt และทุกอย่างจะดี? หรือไม่ทิ้งตัวชี้ห้อย?

คุณยังควรทำสิ่งนี้หรือไม่:

DoSomething(myClassObject.Get());

void DoSomething(std::shared_ptr<myClass>* arg1)
{
   (*arg1)->someField = 4;
}

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

ขอขอบคุณ.


14
const std::shared_ptr<myClass>& arg1
Captain Obvlious

3
วิธีที่สองเสียวิธีแรกเป็นสำนวนถ้าคุณต้องการฟังก์ชันของคุณเพื่อแบ่งปันความเป็นเจ้าของ แต่DoSomethingจำเป็นต้องมีกรรมสิทธิ์ร่วมด้วยหรือไม่? ดูเหมือนว่าควรใช้การอ้างอิงแทน ...
ildjarn

8
@SteveH: ไม่ได้ แต่ทำไมฟังก์ชั่นของคุณควรบังคับใช้ความหมายความเป็นเจ้าของวัตถุพิเศษกับผู้โทรหากไม่ต้องการจริงๆ ฟังก์ชันของคุณไม่มีอะไรที่จะไม่ดีไปกว่าvoid DoSomething(myClass& arg1)นี้
ildjarn

2
@SteveH: จุดประสงค์ทั้งหมดของสมาร์ทพอยน์เตอร์คือการจัดการกับปัญหาการเป็นเจ้าของวัตถุทั่วไป - หากคุณไม่มีสิ่งเหล่านี้คุณไม่ควรใช้ตัวชี้อัจฉริยะตั้งแต่แรก ; -] shared_ptr<>โดยเฉพาะอย่างยิ่งคุณต้องส่งผ่านมูลค่าเพื่อที่จะได้เป็นเจ้าของร่วมกัน
ildjarn

4
ไม่เกี่ยวข้อง: โดยทั่วไปstd::make_sharedไม่เพียง แต่มีประสิทธิภาพมากกว่า แต่ยังปลอดภัยกว่าตัวstd::shared_ptrสร้างด้วย
R.Martinho Fernandes

คำตอบ:


171

ฉันต้องการส่งตัวชี้ที่ใช้ร่วมกันไปยังฟังก์ชัน คุณสามารถช่วยฉันด้วย?

ได้เลยฉันช่วยคุณได้ ฉันถือว่าคุณมีความเข้าใจเกี่ยวกับความหมายความเป็นเจ้าของใน C ++ เป็นเช่นนั้นจริงหรือ?

ใช่ฉันสบายใจพอสมควรกับเรื่องนี้

ดี.

โอเคฉันคิดได้แค่สองเหตุผลที่จะshared_ptrโต้แย้ง:

  1. ฟังก์ชั่นต้องการแบ่งปันความเป็นเจ้าของวัตถุ
  2. ฟังก์ชั่นนี้ทำงานบางอย่างที่ทำงานเฉพาะกับshared_ptrs

สนใจอันไหน

ฉันกำลังมองหาคำตอบทั่วไปดังนั้นฉันจึงสนใจทั้งสองอย่าง ฉันอยากรู้ว่าคุณหมายถึงอะไรในกรณีที่ 2

ตัวอย่างของฟังก์ชันดังกล่าว ได้แก่ ตัวstd::static_pointer_castเปรียบเทียบแบบกำหนดเองหรือเพรดิเคต ตัวอย่างเช่นหากคุณต้องการค้นหา shared_ptr ที่ไม่ซ้ำกันทั้งหมดจากเวกเตอร์คุณต้องมีเพรดิเคตดังกล่าว

อ่าเมื่อฟังก์ชันต้องจัดการตัวชี้อัจฉริยะเองจริงๆ

ตรง

ในกรณีนั้นฉันคิดว่าเราควรผ่านการอ้างอิง

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

ตกลงเข้าใจแล้ว เรามาพูดถึงสถานการณ์อื่น ๆ

คนที่คุณเป็นเจ้าของร่วมกัน? ตกลง. คุณเป็นเจ้าของร่วมกันได้shared_ptrอย่างไร?

โดยการคัดลอก.

จากนั้นฟังก์ชันจะต้องทำสำเนาของ a shared_ptrถูกต้องหรือไม่?

เห็นได้ชัด. ดังนั้นฉันจึงส่งต่อโดยการอ้างอิงถึง const และคัดลอกไปยังตัวแปรท้องถิ่น?

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

จุดดี. ฉันต้องจำไว้ว่าบทความ " ต้องการความเร็วหรือไม่ผ่านค่า " บ่อยขึ้น

เดี๋ยวก่อนจะเกิดอะไรขึ้นถ้าฟังก์ชันเก็บshared_ptrตัวแปรในสมาชิกเช่น? จะไม่ทำสำเนาซ้ำซ้อน?

ฟังก์ชันสามารถย้ายshared_ptrอาร์กิวเมนต์ไปยังที่เก็บข้อมูลได้ การย้าย a shared_ptrมีราคาถูกเนื่องจากไม่เปลี่ยนแปลงการนับอ้างอิงใด ๆ

อ่าความคิดที่ดี

แต่ฉันกำลังคิดถึงสถานการณ์ที่สาม: จะเกิดอะไรขึ้นถ้าคุณไม่ต้องการที่จะจัดการshared_ptrหรือร่วมเป็นเจ้าของ?

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

และฉันควรใช้ pointee โดยอ้างอิงหรือตามค่า?

ใช้กฎตามปกติ ตัวชี้อัจฉริยะจะไม่เปลี่ยนแปลงอะไร

ส่งผ่านค่าถ้าฉันจะคัดลอกส่งต่อโดยอ้างอิงหากฉันต้องการหลีกเลี่ยงสำเนา

ขวา.

อืม. ฉันคิดว่าคุณลืมสถานการณ์อื่นไปแล้ว จะเป็นอย่างไรหากฉันต้องการร่วมเป็นเจ้าของ แต่ขึ้นอยู่กับเงื่อนไขบางประการเท่านั้น

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

ฉันเสี่ยงต่อการทำสำเนาซ้ำซ้อนหนึ่งชุดในตัวเลือกแรกและสูญเสียโอกาสในการย้ายครั้งที่สอง ฉันไม่สามารถกินเค้กได้หรือไม่?

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

ฉันคิดว่ามันครอบคลุมสถานการณ์ที่เป็นไปได้ทั้งหมด ขอบคุณมาก.


2
@ จอน: เพื่ออะไร? นี้เป็นเรื่องเกี่ยวกับสิ่งที่ฉันควรจะทำอย่างไรหรือฉันควรทำอย่างไรB ฉันคิดว่าเราทุกคนรู้วิธีส่งผ่านวัตถุด้วยค่า / การอ้างอิงไม่ใช่เหรอ?
sbi

9
เนื่องจากร้อยแก้วเช่น "หมายความว่าฉันจะส่งต่อโดยการอ้างอิงถึง const เพื่อทำสำเนาไม่ใช่นั่นเป็นการมองในแง่ร้าย" จึงทำให้ผู้เริ่มต้นสับสน การส่งผ่านการอ้างอิงไปยัง const ไม่ได้ทำสำเนา
จอน

1
@Martinho ฉันไม่เห็นด้วยกับกฎ "ถ้าคุณต้องการที่จะจัดการกับพอยน์เตอร์ให้ใช้พอยน์ตี้" ตามที่ระบุไว้ในคำตอบนี้การไม่เป็นเจ้าของวัตถุชั่วคราวอาจเป็นอันตรายได้ ในความคิดของฉันค่าเริ่มต้นควรจะส่งสำเนาสำหรับการเป็นเจ้าของชั่วคราวเพื่อรับประกันความถูกต้องของวัตถุตลอดระยะเวลาของการเรียกใช้ฟังก์ชัน
radman

@radman ไม่มีสถานการณ์ในคำตอบที่คุณเชื่อมโยงไปใช้กฎนั้นดังนั้นจึงไม่เกี่ยวข้องโดยสิ้นเชิง โปรดเขียน SSCCE ให้ฉันโดยที่คุณส่งต่อโดยอ้างอิงจาก a shared_ptr<T> ptr;(เช่นvoid f(T&)เรียกว่าด้วยf(*ptr)) และวัตถุนั้นไม่อยู่ได้นานกว่าการโทร หรือเขียนข้อความที่คุณส่งผ่านมูลค่าvoid f(T)และปัญหาการประท้วง
R. Martinho Fernandes

@Martinho ตรวจสอบรหัสตัวอย่างของฉันสำหรับตัวอย่างง่ายๆที่มีอยู่ในตัวเองที่ถูกต้อง ตัวอย่างสำหรับvoid f(T&)และเกี่ยวข้องกับเธรด ฉันคิดว่าปัญหาของฉันคือน้ำเสียงของคำตอบของคุณไม่สนับสนุนการเป็นเจ้าของ shared_ptr ซึ่งเป็นวิธีที่ปลอดภัยที่สุดใช้งานง่ายที่สุดและซับซ้อนน้อยที่สุดในการใช้ ฉันจะไม่แนะนำอย่างยิ่งที่จะแนะนำให้ผู้เริ่มต้นดึงข้อมูลอ้างอิงไปยังข้อมูลที่เป็นของ shared_ptr <> ความเป็นไปได้ที่จะนำไปใช้ในทางที่ผิดนั้นดีมากและประโยชน์ก็น้อยมาก
radman

22

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

void DoSomething(myClass * p);

DoSomething(myClass_shared_ptr.get());
DoSomething(myClass_unique_ptr.get());

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


8
หากคุณใช้ตัวชี้ทำไมไม่ใช้การอ้างอิง DoSomething(*a_smart_ptr)
Xeo

5
@Xeo คุณพูดถูก - นั่นจะดีกว่านี้ บางครั้งคุณต้องยอมให้มีตัวชี้ NULL เป็นไปได้
Mark Ransom

1
หากคุณมีการประชุมของการใช้ตัวชี้ดิบเป็นพารามิเตอร์ที่ส่งออกก็ง่ายต่อการมองเห็นในการเรียกร้องรหัสที่ตัวแปรอาจมีการเปลี่ยนแปลงเช่น: เปรียบเทียบการfun(&x) fun(x)ในตัวอย่างหลังxสามารถส่งผ่านด้วยค่าหรือเป็น const ref ตามที่ระบุไว้ข้างต้นยังอนุญาตให้คุณผ่านnullptrหากคุณไม่สนใจในผลลัพธ์ ...
Andreas Magnusson

2
@AndreasMagnusson: การประชุมพารามิเตอร์ตัวชี้เป็นเอาต์พุตนั้นสามารถให้ความรู้สึกปลอดภัยที่ผิดพลาดเมื่อจัดการกับตัวชี้ที่ส่งมาจากแหล่งอื่น เนื่องจากในกรณีนี้การโทรเป็นเรื่องสนุก (x) และ * x ถูกแก้ไขและแหล่งที่มาไม่มี & x เพื่อเตือนคุณ
Zan Lynx

จะเกิดอะไรขึ้นถ้าการเรียกใช้ฟังก์ชันอยู่ได้นานกว่าขอบเขตของผู้โทร? มีความเป็นไปได้ที่จะมีการเรียกตัวทำลายของ ptr ที่ใช้ร่วมกันและตอนนี้ฟังก์ชันจะพยายามเข้าถึงหน่วยความจำที่ถูกลบส่งผลให้ UB
Sridhar Thiagarajan

4

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

ฉันจะหลีกเลี่ยงตัวชี้ไปที่ shared_ptr <> เนื่องจากมันเอาชนะจุดประสงค์ในขณะนี้คุณกำลังจัดการกับ raw_pointers อีกครั้ง


2

Passing-by-value ในตัวอย่างแรกของคุณปลอดภัย แต่มีสำนวนที่ดีกว่า ผ่านการอ้างอิง const เมื่อเป็นไปได้ - ฉันจะตอบว่าใช่แม้ว่าจะจัดการกับตัวชี้อัจฉริยะก็ตาม ตัวอย่างที่สองของคุณจะไม่เสียว่า !???แต่มันก็มาก งี่เง่าไม่ทำอะไรให้สำเร็จและเอาชนะส่วนหนึ่งของตัวชี้ที่ชาญฉลาดและจะทำให้คุณอยู่ในโลกแห่งความเจ็บปวดเมื่อคุณพยายามลดทอนและปรับเปลี่ยนสิ่งต่างๆ


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

2
@ildjarn: การผ่านการอ้างอิงคงที่เป็นเรื่องปกติในกรณีนี้ shared_ptrรับประกันว่าต้นฉบับของผู้โทรจะอยู่ได้นานกว่าการเรียกใช้ฟังก์ชันดังนั้นฟังก์ชันนี้จึงปลอดภัยในการใช้ไฟล์shared_ptr<> const &. หากจำเป็นต้องจัดเก็บตัวชี้ไว้ในภายหลังจะต้องคัดลอก (ณ จุดนี้ต้องทำสำเนาแทนที่จะถือเอกสารอ้างอิง) แต่ไม่จำเป็นต้องเสียค่าใช้จ่ายในการคัดลอกเว้นแต่คุณจะต้องทำ มัน. ฉันจะส่งต่อการอ้างอิงอย่างต่อเนื่องในกรณีนี้ ....
David Rodríguez - dribeas

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

2
... ในทางกลับกันฟังก์ชันไม่จำเป็นต้องยืดอายุการใช้งานของวัตถุคุณสามารถลบออกshared_ptrจากอินเทอร์เฟซและส่งการconstอ้างอิงธรรมดา ( ) ไปยังวัตถุปลายแหลม
David Rodríguez - น้ำลายไหล

3
@ เดวิด: จะเกิดอะไรขึ้นถ้าผู้บริโภค API ของคุณไม่ได้ใช้shared_ptr<>เพื่อเริ่มต้นด้วย? ตอนนี้ API ของคุณเป็นเพียงความเจ็บปวดที่คอเนื่องจากมันบังคับให้ผู้โทรเปลี่ยนความหมายอายุการใช้งานวัตถุโดยไม่มีจุดหมายเพียงเพื่อใช้มัน ฉันไม่เห็นสิ่งใดที่ควรค่าแก่การสนับสนุนในเรื่องนี้นอกจากพูดว่า "มันไม่ได้ทำร้ายอะไรในทางเทคนิค" ฉันไม่เห็นอะไรที่ขัดแย้งเกี่ยวกับ "ถ้าคุณไม่ต้องการความเป็นเจ้าของร่วมก็อย่าใช้shared_ptr<>"
ildjarn

0

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

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

สำนวนที่จะแสดงสิ่งนี้คือผ่าน: const ref เช่นนั้น

void DoSomething(const std::shared_ptr<myClass>& arg)

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

CAVEAT: ซึ่งหมายความว่าหากมีคนโทรshared_ptr::resetมาก่อนที่คุณจะเรียกใช้ฟังก์ชันของคุณและในขณะนั้นเป็น shared_ptr สุดท้ายที่เป็นเจ้าของ raw_ptr วัตถุของคุณจะถูกทำลายและฟังก์ชันของคุณจะจัดการกับตัวชี้ห้อยไปยังวัตถุที่ถูกทำลาย อันตรายมาก!!!

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