ฉันคิดถึงคำถามนี้ในช่วงสี่ปีที่ผ่านมา ฉันได้ข้อสรุปว่าคำอธิบายส่วนใหญ่เกี่ยวpush_backกับ vs. emplace_backคิดถึงภาพเต็ม
ปีที่แล้วผมได้นำเสนอที่ C ++ ตอนนี้ในประเภทหักใน C ฉันเริ่มพูดถึงpush_backvs. emplace_backที่ 13:49 แต่มีข้อมูลที่เป็นประโยชน์ที่ให้หลักฐานสนับสนุนก่อนหน้านั้น
ความแตกต่างหลักที่แท้จริงเกี่ยวข้องกับตัวสร้างโดยนัยกับตัวสร้างที่ชัดเจน พิจารณากรณีที่เรามีอาร์กิวเมนต์เดียวที่เราต้องการส่งต่อไปหรือpush_backemplace_back
std::vector<T> v;
v.push_back(x);
v.emplace_back(x);
หลังจากคอมไพเลอร์การปรับให้เหมาะสมของคุณได้รับสิ่งนี้แล้วไม่มีความแตกต่างระหว่างสองข้อความนี้ในแง่ของโค้ดที่สร้างขึ้น ภูมิปัญญาดั้งเดิมคือว่าpush_backจะสร้างวัตถุชั่วคราวซึ่งจะถูกย้ายเข้าไปvในขณะที่emplace_backจะส่งต่อการโต้เถียงตามและสร้างมันโดยตรงในสถานที่โดยไม่มีการคัดลอกหรือย้าย สิ่งนี้อาจเป็นจริงตามรหัสที่เขียนในไลบรารีมาตรฐาน แต่มันทำให้เข้าใจผิดว่างานเพิ่มประสิทธิภาพของคอมไพเลอร์คือการสร้างรหัสที่คุณเขียน การเพิ่มประสิทธิภาพของคอมไพเลอร์คือการสร้างรหัสที่คุณจะเขียนถ้าคุณเป็นผู้เชี่ยวชาญในการปรับแต่งเฉพาะแพลตฟอร์มและไม่สนใจเกี่ยวกับความสามารถในการบำรุงรักษาเพียงประสิทธิภาพ
ความแตกต่างที่เกิดขึ้นจริงระหว่างข้อความทั้งสองนี้คือยิ่งมีพลังมากขึ้นemplace_backจะเรียกใช้ตัวสร้างประเภทใดก็ได้ในขณะที่ความระมัดระวังยิ่งกว่าpush_backจะเรียกเฉพาะตัวสร้างที่มีนัยเท่านั้น Constructor โดยปริยายน่าจะปลอดภัย หากคุณสามารถสร้าง a Uจากโดยปริยายได้Tคุณกำลังบอกว่าUสามารถเก็บข้อมูลทั้งหมดไว้ได้Tโดยไม่มีการสูญเสีย มีความปลอดภัยในทุกสถานการณ์ที่จะผ่านTและไม่มีใครจะรังเกียจถ้าคุณทำให้มันเป็นUแทน เป็นตัวอย่างที่ดีของตัวสร้างโดยนัยคือการแปลงจากไปstd::uint32_t std::uint64_tเป็นตัวอย่างที่ไม่ดีของการแปลงนัยคือการdoublestd::uint8_t
เราต้องการที่จะระมัดระวังในการเขียนโปรแกรมของเรา เราไม่ต้องการที่จะใช้คุณสมบัติที่มีประสิทธิภาพเพราะยิ่งคุณสมบัติมีประสิทธิภาพมากขึ้นเท่าไหร่ก็จะยิ่งทำสิ่งที่ไม่ถูกต้องหรือไม่คาดคิดได้ง่ายขึ้นเท่านั้น emplace_backถ้าคุณตั้งใจจะโทรก่อสร้างอย่างชัดเจนแล้วคุณจำเป็นต้องใช้พลังงานของ push_backหากคุณต้องการที่จะเรียกก่อสร้างเพียงส่อติดกับความปลอดภัยของ
ตัวอย่าง
std::vector<std::unique_ptr<T>> v;
T a;
v.emplace_back(std::addressof(a)); // compiles
v.push_back(std::addressof(a)); // fails to compile
std::unique_ptr<T>T *มีคอนสตรัคอย่างชัดเจนจาก เพราะemplace_backสามารถเรียกคอนสตรัคเตอร์ที่ชัดเจนผ่านการคอมไพล์พอยน์เตอร์ที่ไม่ได้เป็นเจ้าของได้ดี อย่างไรก็ตามเมื่อvออกไปนอกขอบเขต destructor จะพยายามโทรหาdeleteตัวชี้นั้นซึ่งไม่ได้ถูกจัดสรรโดยnewเพราะมันเป็นเพียงวัตถุสแต็ก สิ่งนี้นำไปสู่พฤติกรรมที่ไม่ได้กำหนด
นี่ไม่ใช่แค่รหัสที่คิดค้น นี่เป็นข้อผิดพลาดการผลิตจริงที่ฉันพบ รหัสคือstd::vector<T *>แต่มันเป็นเจ้าของเนื้อหา ในฐานะที่เป็นส่วนหนึ่งของการโยกย้ายไปยัง C ++ 11 ผมอย่างถูกต้องเปลี่ยนแปลงT *เพื่อstd::unique_ptr<T>ที่จะแสดงให้เห็นว่าเวกเตอร์ที่เป็นเจ้าของหน่วยความจำ แต่ผมก็ basing การเปลี่ยนแปลงเหล่านี้ออกจากความเข้าใจของฉันในปี 2012 ในระหว่างที่ผมคิดว่า "ทุกอย่างไม่ emplace_back push_back สามารถทำและอื่น ๆ ดังนั้นทำไมฉันจะเคยใช้ push_back?" ดังนั้นผมก็เปลี่ยนไปpush_backemplace_back
หากฉันทิ้งรหัสไว้เป็นการใช้ที่ปลอดภัยกว่าpush_backฉันจะจับข้อผิดพลาดอันยาวนานนี้ได้ทันทีและจะถูกมองว่าเป็นความสำเร็จในการอัปเกรดเป็น C ++ 11 แต่ฉันปิดบังข้อบกพร่องและไม่พบมันจนกว่าจะถึงเดือนต่อมา