ฉันคิดถึงคำถามนี้ในช่วงสี่ปีที่ผ่านมา ฉันได้ข้อสรุปว่าคำอธิบายส่วนใหญ่เกี่ยวpush_back
กับ vs. emplace_back
คิดถึงภาพเต็ม
ปีที่แล้วผมได้นำเสนอที่ C ++ ตอนนี้ในประเภทหักใน C ฉันเริ่มพูดถึงpush_back
vs. emplace_back
ที่ 13:49 แต่มีข้อมูลที่เป็นประโยชน์ที่ให้หลักฐานสนับสนุนก่อนหน้านั้น
ความแตกต่างหลักที่แท้จริงเกี่ยวข้องกับตัวสร้างโดยนัยกับตัวสร้างที่ชัดเจน พิจารณากรณีที่เรามีอาร์กิวเมนต์เดียวที่เราต้องการส่งต่อไปหรือpush_back
emplace_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
เป็นตัวอย่างที่ไม่ดีของการแปลงนัยคือการdouble
std::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_back
emplace_back
หากฉันทิ้งรหัสไว้เป็นการใช้ที่ปลอดภัยกว่าpush_back
ฉันจะจับข้อผิดพลาดอันยาวนานนี้ได้ทันทีและจะถูกมองว่าเป็นความสำเร็จในการอัปเกรดเป็น C ++ 11 แต่ฉันปิดบังข้อบกพร่องและไม่พบมันจนกว่าจะถึงเดือนต่อมา