รหัสต่อไปนี้อาจช่วยให้คุณเข้าใจ "แนวคิดภาพรวม" ว่าinsert()แตกต่างจากemplace():
#include <iostream>
#include <unordered_map>
#include <utility>
struct Foo {
static int foo_counter;
int val;
Foo() { val = foo_counter++;
std::cout << "Foo() with val: " << val << '\n';
}
Foo(int value) : val(value) { foo_counter++;
std::cout << "Foo(int) with val: " << val << '\n';
}
Foo(Foo& f2) { val = foo_counter++;
std::cout << "Foo(Foo &) with val: " << val
<< " \tcreated from: \t" << f2.val << '\n';
}
Foo(const Foo& f2) { val = foo_counter++;
std::cout << "Foo(const Foo &) with val: " << val
<< " \tcreated from: \t" << f2.val << '\n';
}
Foo(Foo&& f2) { val = foo_counter++;
std::cout << "Foo(Foo&&) moving: " << f2.val
<< " \tand changing it to:\t" << val << '\n';
}
~Foo() { std::cout << "~Foo() destroying: " << val << '\n'; }
Foo& operator=(const Foo& rhs) {
std::cout << "Foo& operator=(const Foo& rhs) with rhs.val: " << rhs.val
<< " \tcalled with lhs.val = \t" << val
<< " \tChanging lhs.val to: \t" << rhs.val << '\n';
val = rhs.val;
return *this;
}
bool operator==(const Foo &rhs) const { return val == rhs.val; }
bool operator<(const Foo &rhs) const { return val < rhs.val; }
};
int Foo::foo_counter = 0;
namespace std {
template<> struct hash<Foo> {
std::size_t operator()(const Foo &f) const {
return std::hash<int>{}(f.val);
}
};
}
int main()
{
std::unordered_map<Foo, int> umap;
Foo foo0, foo1, foo2, foo3;
int d;
std::cout << "\numap.insert(std::pair<Foo, int>(foo0, d))\n";
umap.insert(std::pair<Foo, int>(foo0, d));
std::cout << "\numap.insert(std::move(std::pair<Foo, int>(foo1, d)))\n";
umap.insert(std::move(std::pair<Foo, int>(foo1, d)));
std::cout << "\nstd::pair<Foo, int> pair(foo2, d)\n";
std::pair<Foo, int> pair(foo2, d);
std::cout << "\numap.insert(pair)\n";
umap.insert(pair);
std::cout << "\numap.emplace(foo3, d)\n";
umap.emplace(foo3, d);
std::cout << "\numap.emplace(11, d)\n";
umap.emplace(11, d);
std::cout << "\numap.insert({12, d})\n";
umap.insert({12, d});
std::cout.flush();
}
ผลลัพธ์ที่ฉันได้รับคือ:
Foo() with val: 0
Foo() with val: 1
Foo() with val: 2
Foo() with val: 3
umap.insert(std::pair<Foo, int>(foo0, d))
Foo(Foo &) with val: 4 created from: 0
Foo(Foo&&) moving: 4 and changing it to: 5
~Foo() destroying: 4
umap.insert(std::move(std::pair<Foo, int>(foo1, d)))
Foo(Foo &) with val: 6 created from: 1
Foo(Foo&&) moving: 6 and changing it to: 7
~Foo() destroying: 6
std::pair<Foo, int> pair(foo2, d)
Foo(Foo &) with val: 8 created from: 2
umap.insert(pair)
Foo(const Foo &) with val: 9 created from: 8
umap.emplace(foo3, d)
Foo(Foo &) with val: 10 created from: 3
umap.emplace(11, d)
Foo(int) with val: 11
umap.insert({12, d})
Foo(int) with val: 12
Foo(const Foo &) with val: 13 created from: 12
~Foo() destroying: 12
~Foo() destroying: 8
~Foo() destroying: 3
~Foo() destroying: 2
~Foo() destroying: 1
~Foo() destroying: 0
~Foo() destroying: 13
~Foo() destroying: 11
~Foo() destroying: 5
~Foo() destroying: 10
~Foo() destroying: 7
~Foo() destroying: 9
สังเกตว่า:
unordered_mapเสมอภายในร้านค้าFooวัตถุ (และไม่ได้พูด, Foo *S) เป็นกุญแจที่จะถูกทำลายได้ทุกเมื่อunordered_mapถูกทำลาย ที่นี่unordered_mapคีย์ภายในคือ foos 13, 11, 5, 10, 7 และ 9
- ดังนั้นในทางเทคนิค
unordered_mapจริงๆแล้วเราจะจัดเก็บstd::pair<const Foo, int>วัตถุซึ่งจะเก็บFooวัตถุไว้ แต่หากต้องการทำความเข้าใจ "แนวคิดภาพรวม" ว่าemplace()แตกต่างจากความแตกต่างอย่างไรinsert()(ดูช่องที่ไฮไลต์ด้านล่าง) คุณสามารถจินตนาการถึงวัตถุนี้ชั่วคราวว่าstd::pairอยู่เฉยๆโดยสิ้นเชิง เมื่อคุณเข้าใจ "แนวคิดภาพรวม" นี้แล้วสิ่งสำคัญคือต้องสำรองข้อมูลและทำความเข้าใจวิธีการใช้std::pairวัตถุตัวกลางนี้โดยการunordered_mapแนะนำเทคนิคที่ละเอียดอ่อน แต่สำคัญ
ใส่แต่ละfoo0, foo1และfoo2จำเป็นต้องมี 2 สายให้เป็นหนึ่งในFoo's ก่อสร้างคัดลอก / ย้ายและ 2 โทรไปFoo ' s destructor (อย่างที่ผมอธิบาย):
ก. การแทรกfoo0และfoo1สร้างอ็อบเจ็กต์ชั่วคราว ( foo4และfoo6ตามลำดับ) ซึ่งตัวทำลายถูกเรียกใช้ทันทีหลังจากการแทรกเสร็จสิ้น นอกจากนี้Foos ภายในของ unordered_map (ซึ่งก็คือFoos 5 และ 7) ยังมีการเรียกตัวทำลายของมันเมื่อ unordered_map ถูกทำลาย
ข. ในการแทรกก่อนfoo2อื่นเราได้สร้างวัตถุคู่ที่ไม่ใช่ชั่วคราว (เรียกว่าpair) อย่างชัดเจนซึ่งเรียกว่าตัวFooสร้างการคัดลอกบนfoo2(สร้างfoo8เป็นสมาชิกภายในของpair) จากนั้นเราinsert()แก้ไขคู่นี้ซึ่งส่งผลให้unordered_mapเรียกตัวสร้างสำเนาอีกครั้ง (เปิดfoo8) เพื่อสร้างสำเนาภายใน ( foo9) ของตัวเอง เช่นเดียวกับfoos 0 และ 1 ผลลัพธ์ที่ได้คือตัวทำลายสองตัวที่เรียกการแทรกนี้โดยมีข้อแตกต่างเพียงอย่างเดียวfoo8คือตัวทำลายของถูกเรียกก็ต่อเมื่อเราไปถึงจุดสิ้นสุดmain()แทนที่จะถูกเรียกทันทีหลังจากinsert()เสร็จ
การแทนที่foo3ส่งผลให้มีการเรียกตัวสร้างสำเนา / ย้ายเพียง 1 รายการ (สร้างfoo10ภายในในunordered_map) และเพียง 1 การเรียกไปยังตัวFooทำลายของ (ฉันจะกลับไปในภายหลัง)
สำหรับfoo11เราส่งจำนวนเต็ม 11 โดยตรงไปemplace(11, d)เพื่อที่unordered_mapจะเรียกตัวFoo(int)สร้างในขณะที่การดำเนินการอยู่ในemplace()เมธอด ซึ่งแตกต่างจากใน (2) และ (3) เราไม่จำเป็นต้องมีfooวัตถุก่อนออกเพื่อทำสิ่งนี้ด้วยซ้ำ ที่สำคัญสังเกตว่ามีการเรียกตัวFooสร้างเพียง 1 ครั้งเท่านั้น(ซึ่งสร้างขึ้นfoo11)
จากนั้นเราจะผ่านจำนวนเต็ม 12 insert({12, d})โดยตรงกับ ซึ่งแตกต่างจากemplace(11, d)(ซึ่งการเรียกคืนส่งผลให้มีการเรียกไปยังคอนFooสตรัคเตอร์เพียง 1 ครั้ง) การเรียกนี้จะinsert({12, d})ทำให้เกิดการเรียกใช้คอนFooสตรัคเตอร์สองครั้ง (การสร้างfoo12และfoo13)
สิ่งนี้แสดงให้เห็นความแตกต่างของ "ภาพใหญ่" หลักระหว่างinsert()และemplace()คืออะไร:
ในขณะที่การใช้insert() เกือบตลอดเวลาจำเป็นต้องมีการสร้างหรือการมีอยู่ของFooวัตถุบางอย่างในmain()ขอบเขต (ตามด้วยการคัดลอกหรือการย้าย) หากใช้emplace()การเรียกไปยังตัวFooสร้างจะทำภายในทั้งหมดในunordered_map(กล่าวคืออยู่ในขอบเขตของemplace()นิยามของวิธีการ) อาร์กิวเมนต์สำหรับคีย์ที่คุณส่งผ่านจะemplace()ถูกส่งต่อโดยตรงไปยังการFooเรียกคอนสตรัคเตอร์ภายในunordered_map::emplace()นิยามของ (รายละเอียดเพิ่มเติมที่เป็นทางเลือก: โดยที่อ็อบเจ็กต์ที่สร้างขึ้นใหม่นี้จะถูกรวมเข้ากับunordered_mapตัวแปรสมาชิกตัวใดตัวหนึ่งทันทีเพื่อที่จะไม่มีการเรียกตัวทำลายเมื่อ ใบดำเนินการemplace()และไม่มีการเรียกตัวสร้างการย้ายหรือคัดลอก)
หมายเหตุ: เหตุผลที่ " เกือบ " ใน " เกือบตลอดเวลา " ข้างต้นอธิบายไว้ใน I) ด้านล่าง
- ต่อ: สาเหตุที่การเรียก
umap.emplace(foo3, d)ตัวFooสร้างการคัดลอกที่ไม่ใช่ const มีดังต่อไปนี้: เนื่องจากเราใช้emplace()คอมไพเลอร์จึงรู้ว่าfoo3( Fooออบเจ็กต์ที่ไม่ใช่ const ) หมายถึงอาร์กิวเมนต์สำหรับตัวFooสร้างบางตัว ในกรณีนี้เหมาะสมที่สุดFooคอนสตรัคเป็นที่ไม่ใช่ const Foo(Foo& f2)นวกรรมิกสำเนา นี่คือเหตุผลที่umap.emplace(foo3, d)เรียกว่าตัวสร้างการคัดลอกในขณะที่umap.emplace(11, d)ไม่ได้
บทส่งท้าย:
I. โปรดทราบว่าการโอเวอร์โหลดหนึ่งครั้งinsert()นั้นเทียบเท่ากับ emplace() . ตามที่อธิบายไว้ในหน้า cppreference.comโอเวอร์โหลดtemplate<class P> std::pair<iterator, bool> insert(P&& value)(ซึ่งเป็นโอเวอร์โหลด (2) ของinsert()ในหน้า cppreference.com นี้) จะเทียบเท่ากับemplace(std::forward<P>(value))เทียบเท่ากับ
II. จะไปที่ไหนจากที่นี่?
ก. ลองใช้ซอร์สโค้ดข้างต้นและเอกสารประกอบการศึกษาสำหรับinsert()(เช่นที่นี่ ) และemplace()(เช่นที่นี่ ) ที่พบได้ทั่วไป หากคุณใช้ IDE เช่น eclipse หรือ NetBeans คุณสามารถรับ IDE ของคุณเพื่อบอกคุณได้อย่างง่ายดายว่ามีการเรียกใช้งานเกินพิกัดinsert()หรือemplace()กำลังถูกเรียกใช้ (ใน eclipse เพียงแค่ให้เคอร์เซอร์ของเมาส์นิ่งเหนือการเรียกฟังก์ชันเป็นเวลาหนึ่งวินาที) นี่คือรหัสเพิ่มเติมที่จะลองใช้:
std::cout << "\numap.insert({{" << Foo::foo_counter << ", d}})\n";
umap.insert({{Foo::foo_counter, d}});
std::cout << "\numap.insert(std::pair<const Foo, int>({" << Foo::foo_counter << ", d}))\n";
umap.insert(std::pair<const Foo, int>({Foo::foo_counter, d}));
std::cout << "\numap.insert(std::pair<Foo, int>({" << Foo::foo_counter << ", d}))\n";
umap.insert(std::pair<Foo, int>({Foo::foo_counter, d}));
std::cout << "\numap.insert({std::pair<Foo, int>({" << Foo::foo_counter << ", d})})\n";
umap.insert({std::pair<Foo, int>({Foo::foo_counter, d})});
int cur_foo_counter = Foo::foo_counter;
std::cout << "\numap.insert({{cur_foo_counter, d}, {cur_foo_counter+1, d}}) where "
<< "cur_foo_counter = " << cur_foo_counter << "\n";
umap.insert({{cur_foo_counter, d}, {cur_foo_counter+1, d}});
std::cout << "\numap.insert({{Foo::foo_counter, d}, {Foo::foo_counter+1, d}}) where "
<< "Foo::foo_counter = " << Foo::foo_counter << "\n";
umap.insert({{Foo::foo_counter, d}, {Foo::foo_counter+1, d}});
std::cout << "\numap.insert(std::initializer_list<std::pair<const Foo, int>>({{" << Foo::foo_counter << ", d}}))\n";
umap.insert(std::initializer_list<std::pair<const Foo, int>>({{Foo::foo_counter, d}}));
ในไม่ช้าคุณจะเห็นว่าตัวstd::pairสร้างโอเวอร์โหลดตัวใด(ดูข้อมูลอ้างอิง ) ถูกใช้โดยunordered_mapสามารถมีผลสำคัญต่อจำนวนวัตถุที่ถูกคัดลอกย้ายสร้างและ / หรือทำลายรวมทั้งเมื่อสิ่งนี้เกิดขึ้นทั้งหมด
ข. ดูว่าจะเกิดอะไรขึ้นเมื่อคุณใช้คลาสคอนเทนเนอร์อื่น ๆ (เช่นstd::setหรือstd::unordered_multiset) แทนstd::unordered_mapแทน
ค. ตอนนี้ใช้Gooวัตถุ (เพียงสำเนาที่เปลี่ยนชื่อFoo) แทนintเป็นประเภทช่วงในunordered_map(เช่นใช้unordered_map<Foo, Goo>แทนunordered_map<Foo, int>) และดูจำนวนและตัวGooสร้างที่เรียกว่า (สปอยเลอร์: มีเอฟเฟกต์ แต่มันไม่ได้ดราม่ามาก)