รับประกันสำเนาถูกต้องอย่างไร?


90

ในการประชุมมาตรฐาน ISO C ++ ของ Oulu ปี 2016 ข้อเสนอที่เรียกว่าการคัดลอกรับประกันผ่านหมวดหมู่ค่าแบบง่ายได้รับการโหวตให้เป็น C ++ 17 โดยคณะกรรมการมาตรฐาน

รับประกันสำเนาถูกต้องอย่างไร? ครอบคลุมบางกรณีที่ได้รับอนุญาตให้คัดลอกสำเนาแล้วหรือจำเป็นต้องมีการเปลี่ยนแปลงรหัสเพื่อรับประกันการลอกสำเนาหรือไม่

คำตอบ:


130

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

รับประกันสำเนาสลัดนิยามใหม่จำนวนของ C ++ แนวคิดดังกล่าวว่าสถานการณ์บางอย่างที่ copies / ย้ายอาจจะ elided ไม่จริงกระตุ้นการคัดลอก / ย้ายที่ทั้งหมด คอมไพเลอร์ไม่ได้คัดลอก มาตรฐานกล่าวว่าไม่มีการคัดลอกแบบนี้เกิดขึ้น

พิจารณาฟังก์ชันนี้:

T Func() {return T();}

ภายใต้กฎการคัดลอกที่ไม่รับประกันการคัดลอกสิ่งนี้จะสร้างชั่วคราวจากนั้นย้ายจากชั่วคราวนั้นไปเป็นค่าส่งคืนของฟังก์ชัน การดำเนินการย้ายนั้นอาจถูกยกเลิก แต่Tยังต้องมีตัวสร้างการย้ายที่สามารถเข้าถึงได้แม้ว่าจะไม่เคยใช้ก็ตาม

ในทำนองเดียวกัน:

T t = Func();

tนี่คือการเริ่มต้นของการคัดลอก สิ่งนี้จะคัดลอกเริ่มต้นtด้วยค่าส่งคืนของFunc. อย่างไรก็ตามTยังคงต้องมีตัวสร้างการย้ายแม้ว่าจะไม่ถูกเรียกก็ตาม

รับประกันการลอกเลียนแบบกำหนดความหมายของนิพจน์ prvalueใหม่ Pre-C ++ 17 prvalues ​​เป็นวัตถุชั่วคราว ใน C ++ 17 นิพจน์ prvalue เป็นเพียงสิ่งที่สามารถทำให้เกิดเป็นชั่วคราวได้ แต่ยังไม่ใช่ชั่วคราว

หากคุณใช้ prvalue เพื่อเริ่มต้นอ็อบเจ็กต์ประเภท prvalue จะไม่มีการปรากฏชั่วคราว เมื่อคุณทำสิ่งreturn T();นี้จะเริ่มต้นค่าส่งคืนของฟังก์ชันผ่าน prvalue เนื่องจากฟังก์ชันนั้นส่งคืนTจึงไม่มีการสร้างชั่วคราว การเริ่มต้นของ prvalue เพียงแค่เริ่มต้นค่าส่งคืนโดยตรง

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

เมื่อคุณทำT t = Func();, prvalue ของค่าตอบแทนโดยตรงเริ่มต้นวัตถุt; ไม่มีสเตจ "สร้างชั่วคราวและคัดลอก / ย้าย" ตั้งแต่Func()ค่าตอบแทน 's เป็นเทียบเท่า prvalue ไปT(), tจะเริ่มต้นได้โดยตรงโดยตรงเช่นถ้าคุณเคยทำT()T t = T()

หากใช้ prvalue ในลักษณะอื่น prvalue จะทำให้เป็นวัตถุชั่วคราวซึ่งจะถูกใช้ในนิพจน์นั้น (หรือทิ้งหากไม่มีนิพจน์) ดังนั้นหากคุณทำเช่นconst T &rt = Func();นั้นค่า prvalue จะเป็นตัวกำหนดชั่วคราว (ใช้T()เป็นตัวเริ่มต้น) ซึ่งข้อมูลอ้างอิงจะถูกเก็บไว้rtพร้อมกับสิ่งที่ขยายอายุการใช้งานชั่วคราวตามปกติ

สิ่งหนึ่งที่รับประกันว่า elision อนุญาตให้คุณทำคือส่งคืนวัตถุที่ไม่สามารถเคลื่อนที่ได้ ตัวอย่างเช่นlock_guardไม่สามารถคัดลอกหรือย้ายได้ดังนั้นคุณจึงไม่มีฟังก์ชันที่ส่งคืนค่าตามค่า แต่ด้วยการรับประกันสำเนาคุณสามารถทำได้

รับประกัน elision ยังทำงานร่วมกับการเริ่มต้นโดยตรง:

new T(FactoryFunction());

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

ดังนั้นฟังก์ชั่นโรงงานที่ส่งคืนตามค่าจึงสามารถเริ่มต้นหน่วยความจำที่จัดสรรฮีปได้โดยตรงโดยไม่รู้ตัว ตราบใดที่ฟังก์ชั่นเหล่านี้ภายในปฏิบัติตามกฎของการตัดออกสำเนารับประกันของหลักสูตร พวกเขาจะต้องกลับ prvalue Tของประเภท

แน่นอนว่ามันก็ใช้ได้เช่นกัน:

new auto(FactoryFunction());

ในกรณีที่คุณไม่ชอบเขียนชื่อพิมพ์


สิ่งสำคัญคือต้องจำไว้ว่าการรับประกันข้างต้นใช้ได้ผลกับ prvalues ​​เท่านั้น นั่นคือคุณไม่ได้รับการรับประกันเมื่อส่งคืนตัวแปรที่มีชื่อ :

T Func()
{
   T t = ...;
   ...
   return t;
}

ในกรณีนี้tยังต้องมีตัวสร้างการคัดลอก / ย้ายที่สามารถเข้าถึงได้ ใช่คอมไพลเลอร์สามารถเลือกที่จะเพิ่มประสิทธิภาพการคัดลอก / ย้าย แต่คอมไพเลอร์ยังคงต้องตรวจสอบการมีอยู่ของตัวสร้างสำเนา / ย้ายที่สามารถเข้าถึงได้

ดังนั้นจึงไม่มีอะไรเปลี่ยนแปลงสำหรับการเพิ่มประสิทธิภาพค่าตอบแทนที่ตั้งชื่อ (NRVO)


1
@BenVoigt: การใส่ประเภทที่ผู้ใช้กำหนดเองที่ไม่สามารถคัดลอกได้เล็กน้อยลงในรีจิสเตอร์ไม่ใช่สิ่งที่ ABI สามารถทำได้ไม่ว่าจะมี elision หรือไม่ก็ตาม
Nicol Bolas

1
เมื่อกฎเป็นแบบสาธารณะแล้วคุณควรอัปเดตสิ่งนี้ด้วยแนวคิด "prvalues ​​are initializations"
Johannes Schaub - litb

7
@ JohannesSchaub-litb: เป็นเพียง "คลุมเครือ" ถ้าคุณรู้มากเกินไปเกี่ยวกับข้อเล็กน้อยของมาตรฐาน C ++ สำหรับ 99% ของชุมชน C ++ เรารู้ว่า "รับประกันสำเนา elision" หมายถึงอะไร กระดาษจริงที่เสนอคุณลักษณะนี้มีชื่อว่า "รับประกันสำเนาถูกต้อง" การเพิ่ม "ผ่านหมวดหมู่ค่าที่เรียบง่าย" เพียงทำให้ผู้ใช้สับสนและเข้าใจยาก นอกจากนี้ยังเป็นการเรียกชื่อผิดเนื่องจากกฎเหล่านี้ไม่ได้ทำให้กฎเกี่ยวกับหมวดหมู่ค่าต่างๆ ไม่ว่าคุณจะชอบหรือไม่ก็ตามคำว่า "รับประกันสำเนาถูกต้อง" หมายถึงคุณลักษณะนี้และไม่มีอะไรอื่น
Nicol Bolas

1
ฉันจึงต้องการที่จะรับ prvalue และพกติดตัวไปได้ ฉันเดาว่านี่เป็นเพียง (ภาพเดียว) std::function<T()>จริงๆ
Yakk - Adam Nevraumont

1
@LukasSalich: นั่นคือคำถาม C ++ 11 คำตอบนี้เกี่ยวกับคุณลักษณะ C ++ 17
Nicol Bolas
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.