push_back กับ emplace_back


761

ฉันบิตสับสนเกี่ยวกับความแตกต่างระหว่างและpush_backemplace_back

void emplace_back(Type&& _Val);
void push_back(const Type& _Val);
void push_back(Type&& _Val);

เนื่องจากมีการใช้งานpush_backมากเกินไปในการอ้างอิงค่า rvalue ฉันไม่เห็นเลยว่าจุดประสงค์ของการemplace_backเป็นอย่างไร


11
มีการอ่านที่ดีที่นี่: open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2642.pdf
Johan Kotlinski

16
โปรดทราบว่า (ตามที่โธมัสพูดด้านล่าง) รหัสในคำถามมาจากการจำลองของ MSVS ของ C ++ 0x ไม่ใช่ C ++ 0x ที่แท้จริง
me22

5
กระดาษดีกว่าที่จะอ่านจะเป็น: open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2345.pdf N2642 ส่วนใหญ่เป็นถ้อยคำสำหรับมาตรฐาน N2345 เป็นกระดาษที่อธิบายและกระตุ้นความคิด
Alan

โปรดทราบว่าแม้ใน MSVC10 จะมีtemplate <class _Valty> void emplace_back(_Valty&& _Val)รุ่นที่ใช้การอ้างอิงสากลซึ่งให้การส่งต่อที่สมบูรณ์แบบไปยังexplicitตัวสร้างอาร์กิวเมนต์เดี่ยว
joki

ที่เกี่ยวข้อง: มีกรณีใดบ้างที่push_backเหมาะสมกว่าemplace_back? กรณีเดียวที่ฉันคิดได้ก็คือถ้าคลาสนั้นสามารถคัดลอกได้ ( T&operator=(constT&)) แต่ไม่สามารถสร้างได้ ( T(constT&)) แต่ฉันไม่สามารถคิดได้ว่าทำไมคนคนหนึ่งถึงต้องการอย่างนั้น
Ben

คำตอบ:


568

นอกจากสิ่งที่ผู้เข้าชมพูดไว้:

ฟังก์ชั่นvoid emplace_back(Type&& _Val)ให้โดย MSCV10 push_back(Type&& _Val)ไม่สอดคล้องและซ้ำซ้อนเพราะในขณะที่คุณสังเกตเห็นว่ามันเป็นอย่างเคร่งครัดเทียบเท่ากับ

แต่รูปแบบ C ++ 0x ที่emplace_backแท้จริงนั้นมีประโยชน์จริง ๆ : void emplace_back(Args&&...);

แทนที่จะใช้value_typeมันจะใช้รายการอาร์กิวเมนต์ที่หลากหลายดังนั้นหมายความว่าตอนนี้คุณสามารถส่งต่ออาร์กิวเมนต์ได้อย่างสมบูรณ์และสร้างวัตถุลงในคอนเทนเนอร์โดยตรงโดยไม่ต้องหยุดชั่วคราวเลย

นั่นมีประโยชน์เพราะไม่ว่า RVO และความหมายของการย้ายความหมายจะมาที่ตารางยังมีกรณีที่ซับซ้อนซึ่ง push_back น่าจะทำสำเนาที่ไม่จำเป็น (หรือย้าย) ตัวอย่างเช่นด้วยinsert()ฟังก์ชันดั้งเดิมของ a std::mapคุณต้องสร้างชั่วคราวซึ่งจะถูกคัดลอกไปยัง a std::pair<Key, Value>ซึ่งจะถูกคัดลอกลงในแผนที่:

std::map<int, Complicated> m;
int anInt = 4;
double aDouble = 5.0;
std::string aString = "C++";

// cross your finger so that the optimizer is really good
m.insert(std::make_pair(4, Complicated(anInt, aDouble, aString))); 

// should be easier for the optimizer
m.emplace(4, anInt, aDouble, aString);

เหตุใดพวกเขาจึงไม่ใช้เวอร์ชันที่ถูกต้องของ emplace_back ใน MSVC ที่จริงมันเรียกร้องให้ผมเกินไปในขณะที่ที่ผ่านมาดังนั้นฉันถามคำถามเดียวกันในVisual C ++ บล็อก นี่คือคำตอบจาก Stephan T Lavavej ผู้ดูแลระบบอย่างเป็นทางการของการใช้งานไลบรารีมาตรฐาน Visual C ++ ที่ Microsoft

ถาม: ฟังก์ชั่น Beta 2 emplace เป็นเพียงตัวยึดตำแหน่งบางประเภทใช่ไหม?

ตอบ: อย่างที่คุณทราบเทมเพลตแบบแปรผันจะไม่ถูกนำมาใช้ใน VC10 เราจำลองพวกเขาด้วยเครื่องจักร preprocessor สำหรับสิ่งที่ต้องการ make_shared<T>(), tuple และสิ่งใหม่ ๆ <functional>ใน เครื่องจักร preprocessor นี้ค่อนข้างยากต่อการใช้และบำรุงรักษา นอกจากนี้ยังส่งผลต่อความเร็วในการรวบรวมอย่างมากเนื่องจากเราต้องรวมส่วนหัวย่อยซ้ำ ๆ เนื่องจากข้อ จำกัด ด้านเวลาและข้อ จำกัด ด้านความเร็วในการรวบรวมเราจึงไม่ได้จำลองเทมเพลต Variadic ในฟังก์ชัน emplace ของเรา

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

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


101
คำชี้แจงที่ว่ามันเป็นปัญหา MSVS10 ไม่ใช่ปัญหา C ++ เป็นส่วนที่สำคัญที่สุดที่นี่ ขอบคุณ
me22

11
ฉันเชื่อว่ารหัส C ++ สุดท้ายของคุณจะไม่ทำงาน pair<const int,Complicated>ไม่มี Constructor ที่รับค่า int, int ตัวอื่น, double และพารามิเตอร์ตัวที่ 4 อย่างไรก็ตามคุณสามารถสร้างวัตถุคู่นี้โดยตรงโดยใช้ตัวสร้างทีละชิ้น รูปแบบจะแตกต่างกันแน่นอน:m.emplace(std::piecewise,std::forward_as_tuple(4),std::forward_as_tuple(anInt,aDouble,aString));
sellibitze

3
เทมเพลตแบบแปรปรวนอย่างมีความสุขจะอยู่ใน VS2013 ตอนนี้อยู่ในหน้าตัวอย่าง
Daniel Earwicker

11
คำตอบนี้ควรได้รับการปรับปรุงเพื่อสะท้อนถึงการพัฒนาใหม่ใน vs2013?
becko

6
หากคุณกำลังใช้ Visual Studio 2013 หรือใหม่กว่าตอนนี้คุณควรได้รับการสนับสนุนสำหรับ "ของจริง" emplace_backตราบใดที่มันถูกนำไปใช้ใน Visual C ++ เมื่อมีการเพิ่มเท็มเพลตแบบแปรผัน
kayleeFrye_onDeck

200

emplace_backไม่ควรใช้อาร์กิวเมนต์เป็นประเภทvector::value_typeแต่ให้ส่งอาร์กิวเมนต์ไปยังตัวสร้างของรายการที่ต่อท้ายแทน

template <class... Args> void emplace_back(Args&&... args); 

เป็นไปได้ที่จะส่งผ่านvalue_typeซึ่งจะถูกส่งต่อไปยังตัวสร้างสำเนา

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

 std::vector<std::string> vec;
 vec.emplace_back(std::string("Hello")); // moves
 std::string s;
 vec.emplace_back(s); //copies

แต่ข้างต้นควรจะเหมือนกันกับสิ่งที่push_backทำ มันอาจจะค่อนข้างมีความหมายสำหรับกรณีการใช้งานเช่น:

 std::vector<std::pair<std::string, std::string> > vec;
 vec.emplace_back(std::string("Hello"), std::string("world")); 
 // should end up invoking this constructor:
 //template<class U, class V> pair(U&& x, V&& y);
 //without making any copies of the strings

2
@ David: แต่แล้วคุณมีการเคลื่อนไหวsในขอบเขตที่ไม่เป็นอันตรายหรือไม่
Matthieu M.

2
มันไม่เป็นอันตรายถ้าคุณไม่ได้วางแผนที่จะใช้ s อีกต่อไปสำหรับค่าของมัน การย้ายไม่ทำให้การใช้งานไม่ถูกต้องการย้ายจะขโมยการจัดสรรหน่วยความจำภายในที่ทำไปแล้วเท่านั้นและปล่อยให้อยู่ในสถานะเริ่มต้น (ไม่มีการต่อยที่จัดสรร) ซึ่งเมื่อถูกทำลายจะปรับได้เหมือนว่าคุณเพิ่งพิมพ์ std :: string str;
David

4
@ David: ฉันไม่แน่ใจว่าวัตถุที่ย้ายจากนั้นจะต้องถูกต้องสำหรับการใช้งานใด ๆ ยกเว้นการทำลายที่ตามมา
Ben Voigt

46
vec.emplace_back("Hello")จะทำงานได้เนื่องจากconst char*ข้อโต้แย้งจะถูกส่งต่อไปยังตัวstringสร้าง emplace_backนี้เป็นจุดรวมของ
Alexandre C.

8
@BenVoigt: วัตถุที่ถูกย้ายจากจะต้องอยู่ในสถานะที่ถูกต้อง (แต่ไม่ได้ระบุ) นี่ไม่ได้แปลว่าคุณสามารถทำการใด ๆ กับมันได้ std::vectorพิจารณา ว่างเปล่าstd::vectorเป็นสถานะที่ถูกต้อง แต่คุณไม่สามารถโทรหาfront()มันได้ นี่หมายความว่าฟังก์ชั่นใด ๆ ที่ไม่มีเงื่อนไขเบื้องต้นยังคงสามารถเรียกใช้ได้ (และ destructors ไม่สามารถมีเงื่อนไขล่วงหน้าได้)
David Stone

96

การเพิ่มประสิทธิภาพสำหรับemplace_backสามารถแสดงในตัวอย่างถัดไป

สำหรับemplace_backนวกรรมิกA (int x_arg)จะถูกเรียก และสำหรับการ push_back A (int x_arg)เรียกครั้งแรกและmove A (A &&rhs)ถูกเรียกในภายหลัง

แน่นอนว่าผู้สร้างจะต้องทำเครื่องหมายว่าเป็นexplicitแต่สำหรับตัวอย่างในปัจจุบันเป็นสิ่งที่ดีที่จะลบชัดเจน

#include <iostream>
#include <vector>
class A
{
public:
  A (int x_arg) : x (x_arg) { std::cout << "A (x_arg)\n"; }
  A () { x = 0; std::cout << "A ()\n"; }
  A (const A &rhs) noexcept { x = rhs.x; std::cout << "A (A &)\n"; }
  A (A &&rhs) noexcept { x = rhs.x; std::cout << "A (A &&)\n"; }

private:
  int x;
};

int main ()
{
  {
    std::vector<A> a;
    std::cout << "call emplace_back:\n";
    a.emplace_back (0);
  }
  {
    std::vector<A> a;
    std::cout << "call push_back:\n";
    a.push_back (1);
  }
  return 0;
}

เอาท์พุท:

call emplace_back:
A (x_arg)

call push_back:
A (x_arg)
A (A &&)

21
+1 สำหรับตัวอย่างโค้ดที่แสดงให้เห็นถึงสิ่งที่เกิดขึ้นจริงเมื่อโทรVSemplace_back push_back
Shawn

ฉันมาที่นี่หลังจากสังเกตเห็นว่าฉันมีรหัสที่เรียกv.emplace_back(x);ว่า x สามารถเคลื่อนย้ายได้อย่างชัดเจน แต่สามารถคัดลอกได้อย่างชัดเจนเท่านั้น ความจริงที่ว่าemplace_backคือ "โดยปริยาย" push_backทำให้ชัดเจนผมคิดว่าการเดินทางไปของฉันฟังก์ชั่นสำหรับท้ายอาจจะ คิด?
เบ็น

หากคุณเรียกa.emplace_backครั้งที่สองตัวสร้างการย้ายจะถูกเรียก!
X Æ A-12


8

emplace_backการดำเนินการตามมาตรฐานจะส่งต่อข้อโต้แย้งไปยังตัวvector<Object>::value_typeสร้างเมื่อเพิ่มลงในเวกเตอร์ ฉันจำได้ว่า Visual Studio ไม่รองรับเทมเพลต Variadic แต่ด้วยเทมเพลต Variadic จะได้รับการสนับสนุนใน Visual Studio 2013 RC ดังนั้นฉันจึงคาดว่าจะมีการเพิ่มลายเซ็นที่สอดคล้องกัน

ด้วยemplace_backหากคุณส่งต่อข้อโต้แย้งไปยังตัวvector<Object>::value_typeสร้างโดยตรงคุณไม่จำเป็นต้องมีประเภทที่สามารถเคลื่อนย้ายหรือคัดลอกได้สำหรับemplace_backฟังก์ชั่นพูดอย่างเคร่งครัด ในvector<NonCopyableNonMovableObject>กรณีนี้ไม่มีประโยชน์เนื่องจากvector<Object>::value_type ต้องการประเภทที่คัดลอกหรือเคลื่อนย้ายได้

แต่โปรดทราบว่านี่อาจเป็นประโยชน์สำหรับstd::map<Key, NonCopyableNonMovableObject>เมื่อคุณจัดสรรรายการในแผนที่จึงไม่จำเป็นต้องย้ายหรือคัดลอกอีกต่อไปซึ่งต่างจากvectorหมายความว่าคุณสามารถใช้งานได้std::mapอย่างมีประสิทธิภาพด้วยประเภทที่แมปซึ่งไม่สามารถคัดลอกหรือเคลื่อนย้ายได้


8

อีกหนึ่งในกรณีของรายการ:

// constructs the elements in place.                                                
emplace_back("element");


//It will create new object and then copy(or move) its value of arguments.
push_back(explicitDataType{"element"});

1

กรณีการใช้งานที่เฉพาะเจาะจงสำหรับemplace_back: ถ้าคุณต้องการที่จะสร้างวัตถุชั่วคราวซึ่งจะถูกผลักดันให้เป็นภาชนะที่ใช้แทนemplace_back push_backมันจะสร้างวัตถุในสถานที่ภายในภาชนะ

หมายเหตุ:

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