ประเด็นของshared_ptr
อินสแตนซ์ที่แตกต่างกันคือการรับประกัน (เท่าที่จะทำได้) ว่าตราบใดที่สิ่งนี้shared_ptr
อยู่ในขอบเขตวัตถุที่ชี้ไปจะยังคงมีอยู่เนื่องจากจำนวนการอ้างอิงจะมีค่าอย่างน้อย 1
Class::only_work_with_sp(boost::shared_ptr<foo> sp)
{
// sp points to an object that cannot be destroyed during this function
}
ดังนั้นโดยใช้การอ้างอิงถึงshared_ptr
คุณจึงปิดใช้งานการรับประกันนั้น ดังนั้นในกรณีที่สองของคุณ:
Class::only_work_with_sp(boost::shared_ptr<foo> &sp) //Again, no copy here
{
...
sp->do_something();
...
}
คุณจะรู้ได้อย่างไรว่าsp->do_something()
จะไม่ระเบิดเนื่องจากตัวชี้โมฆะ?
ทุกอย่างขึ้นอยู่กับสิ่งที่อยู่ในส่วน "... " ของโค้ด จะเกิดอะไรขึ้นถ้าคุณเรียกบางสิ่งในช่วงแรก '... ' ที่มีผลข้างเคียง (บางแห่งในส่วนอื่นของรหัส) ในการล้าง a shared_ptr
ไปยังวัตถุเดียวกันนั้น? และจะเกิดอะไรขึ้นถ้ามันเป็นสิ่งเดียวที่เหลืออยู่ของshared_ptr
วัตถุนั้น? ลาก่อนวัตถุที่คุณกำลังจะลองใช้
ดังนั้นจึงมีสองวิธีในการตอบคำถามนี้:
ตรวจสอบแหล่งที่มาของโปรแกรมทั้งหมดของคุณอย่างรอบคอบจนกว่าคุณจะแน่ใจว่าวัตถุนั้นจะไม่ตายในระหว่างร่างกายของฟังก์ชัน
เปลี่ยนพารามิเตอร์กลับเป็นวัตถุที่แตกต่างกันแทนที่จะเป็นการอ้างอิง
คำแนะนำทั่วไปที่นำไปใช้ที่นี่: อย่ากังวลกับการเปลี่ยนแปลงที่มีความเสี่ยงในโค้ดของคุณเพื่อประสิทธิภาพในการทำงานจนกว่าคุณจะกำหนดเวลาผลิตภัณฑ์ของคุณในสถานการณ์ที่เป็นจริงในผู้สร้างโปรไฟล์และวัดผลสรุปได้ว่าการเปลี่ยนแปลงที่คุณต้องการจะทำจะทำให้ ความแตกต่างอย่างมีนัยสำคัญต่อประสิทธิภาพ
อัปเดตสำหรับผู้แสดงความคิดเห็น JQ
นี่คือตัวอย่างที่สร้างขึ้น มันง่ายอย่างจงใจดังนั้นข้อผิดพลาดจะชัดเจน ในตัวอย่างจริงข้อผิดพลาดไม่ชัดเจนนักเนื่องจากซ่อนอยู่ในชั้นของรายละเอียดที่แท้จริง
เรามีฟังก์ชันที่จะส่งข้อความไปที่ไหนสักแห่ง อาจเป็นข้อความขนาดใหญ่ดังนั้นแทนที่จะใช้ข้อความstd::string
ที่อาจถูกคัดลอกเนื่องจากถูกส่งไปยังที่ต่างๆเราใช้ a shared_ptr
to a string:
void send_message(std::shared_ptr<std::string> msg)
{
std::cout << (*msg.get()) << std::endl;
}
(เราเพียงแค่ "ส่ง" ไปยังคอนโซลสำหรับตัวอย่างนี้)
ตอนนี้เราต้องการเพิ่มสิ่งอำนวยความสะดวกในการจำข้อความก่อนหน้านี้ เราต้องการลักษณะการทำงานต่อไปนี้: ต้องมีตัวแปรที่มีข้อความที่ส่งล่าสุด แต่ในขณะที่กำลังส่งข้อความจะต้องไม่มีข้อความก่อนหน้านี้ (ควรรีเซ็ตตัวแปรก่อนส่ง) ดังนั้นเราจึงประกาศตัวแปรใหม่:
std::shared_ptr<std::string> previous_message;
จากนั้นเราแก้ไขการทำงานของเราตามกฎที่เราระบุ:
void send_message(std::shared_ptr<std::string> msg)
{
previous_message = 0;
std::cout << *msg << std::endl;
previous_message = msg;
}
ดังนั้นก่อนที่เราจะเริ่มส่งเราจะทิ้งข้อความก่อนหน้าปัจจุบันและหลังจากการส่งเสร็จสมบูรณ์เราสามารถจัดเก็บข้อความก่อนหน้าใหม่ได้ ทั้งหมดดี. นี่คือรหัสทดสอบบางส่วน:
send_message(std::shared_ptr<std::string>(new std::string("Hi")));
send_message(previous_message);
และตามที่คาดไว้สิ่งนี้จะพิมพ์Hi!
สองครั้ง
ตอนนี้นายดูแลรักษาผู้ซึ่งดูรหัสและคิดว่า: เฮ้พารามิเตอร์ที่send_message
จะเป็นshared_ptr
:
void send_message(std::shared_ptr<std::string> msg)
เห็นได้ชัดว่าสามารถเปลี่ยนเป็น:
void send_message(const std::shared_ptr<std::string> &msg)
ลองนึกถึงการเพิ่มประสิทธิภาพนี้! (ไม่เป็นไรว่าปกติแล้วเรากำลังจะส่งข้อความขนาดใหญ่ไปยังบางช่องดังนั้นการเพิ่มประสิทธิภาพจะน้อยจนไม่สามารถวัดได้)
แต่ปัญหาที่แท้จริงคือตอนนี้โค้ดทดสอบจะแสดงพฤติกรรมที่ไม่ได้กำหนดไว้ (ในการสร้างการดีบัก Visual C ++ 2010 มันขัดข้อง)
Mr Maintainer รู้สึกประหลาดใจกับสิ่งนี้ แต่เพิ่มการตรวจสอบการป้องกันเพื่อsend_message
พยายามหยุดปัญหาที่เกิดขึ้น:
void send_message(const std::shared_ptr<std::string> &msg)
{
if (msg == 0)
return;
แต่แน่นอนว่ามันยังคงดำเนินต่อไปและหยุดทำงานเพราะmsg
จะไม่เป็นโมฆะเมื่อsend_message
ถูกเรียก
อย่างที่ฉันพูดด้วยรหัสทั้งหมดที่อยู่ใกล้กันในตัวอย่างเล็กน้อยมันง่ายที่จะหาข้อผิดพลาด แต่ในโปรแกรมจริงด้วยความสัมพันธ์ที่ซับซ้อนมากขึ้นระหว่างวัตถุที่เปลี่ยนแปลงได้ซึ่งถือพอยน์เตอร์ซึ่งกันและกันทำให้เกิดข้อผิดพลาดได้ง่ายและยากที่จะสร้างกรณีทดสอบที่จำเป็นเพื่อตรวจจับความผิดพลาด
ทางออกที่ง่ายที่คุณต้องการฟังก์ชั่นเพื่อให้สามารถพึ่งพาshared_ptr
ดำเนินการต่อไปจะไม่ใช่ null ตลอดสำหรับฟังก์ชั่นในการจัดสรรที่แท้จริงของตัวเองมากกว่าการพึ่งพาการอ้างอิงไปยังมีอยู่shared_ptr
shared_ptr
ข้อเสียคือการคัดลอก a shared_ptr
ไม่ฟรี: แม้แต่การใช้งานที่ "ไม่ต้องล็อก" ก็ยังต้องใช้การดำเนินการที่เชื่อมต่อกันเพื่อให้เป็นไปตามการรับประกันเธรด ดังนั้นอาจมีสถานการณ์ที่โปรแกรมสามารถเร่งความเร็วได้อย่างมากโดยการเปลี่ยน a shared_ptr
เป็นไฟล์shared_ptr &
. แต่นี่ไม่ใช่การเปลี่ยนแปลงที่สามารถทำได้อย่างปลอดภัยกับทุกโปรแกรม มันเปลี่ยนความหมายเชิงตรรกะของโปรแกรม
โปรดทราบว่าข้อผิดพลาดที่คล้ายกันจะเกิดขึ้นหากเราใช้std::string
ตลอดแทนที่จะใช้std::shared_ptr<std::string>
และแทนที่จะเป็น
previous_message = 0;
เพื่อล้างข้อความเราพูดว่า:
previous_message.clear();
จากนั้นอาการจะเป็นการส่งข้อความเปล่าโดยไม่ได้ตั้งใจแทนที่จะเป็นพฤติกรรมที่ไม่ได้กำหนด ค่าใช้จ่ายของสำเนาพิเศษของสตริงขนาดใหญ่มากอาจมีความสำคัญมากกว่าต้นทุนการคัดลอก a shared_ptr
มากดังนั้นการแลกเปลี่ยนอาจแตกต่างกัน