Aşağıdaki kod, birbirinden nasıl insert()farklı olduğunun "büyük resim fikrini" anlamanıza yardımcı olabilir emplace():
#include <iostream>
#include <unordered_map>
#include <utility>
//Foo simply outputs what constructor is called with what value.
struct Foo {
static int foo_counter; //Track how many Foo objects have been created.
int val; //This Foo object was the val-th Foo object to be created.
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;
//Create a hash function for Foo in order to use Foo with unordered_map
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;
//Print the statement to be executed and then execute it.
std::cout << "\numap.insert(std::pair<Foo, int>(foo0, d))\n";
umap.insert(std::pair<Foo, int>(foo0, d));
//Side note: equiv. to: umap.insert(std::make_pair(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)));
//Side note: equiv. to: umap.insert(std::make_pair(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();
}
Aldığım çıktı:
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
Dikkat:
Her unordered_mapzaman, dahili olarak Foonesneleri (ve diyelim ki Foo *) anahtarlar olarak depolar ; bunların hepsi yok edildiğinde unordered_mapyok edilir. Burada unordered_mapiç anahtarlar foos 13, 11, 5, 10, 7 ve 9'du.
- Yani teknik olarak,
unordered_mapaslında std::pair<const Foo, int>nesneleri depolar , bu da Foonesneleri depolar . Ancak, emplace()farklı olanın "büyük resim fikrini" anlamak için insert()(aşağıdaki vurgulanan kutuya bakın), bu nesneyi geçicistd::pair olarak tamamen pasif olarak hayal etmek uygundur . Bu "büyük resim fikrini" anladıktan sonra, bu aracı std::pairnesnenin unordered_mapkullanımının ince ama önemli teknikler getirerek nasıl yedeklendiğini ve anlanması önemlidir.
Her takma foo0, foo1ve foo2birine 2 arama gerekli Foobireyin kopyalama / taşıma yapıcılar ve 2 çağrı Foo'destructor lar (şimdi açıklamak gibi):
a. Her birinin yerleştirilmesi foo0ve imha edicinin yerleştirme tamamlandıktan hemen sonra çağrıldığı foo1geçici bir nesne ( foo4ve foo6sırasıyla) oluşturması. Buna ek olarak, unordered_map'in dahili Foos ( Foos ve 5 ve 7 olan), yıkıcıları da unordered_map yok edildiğinde çağrıldı.
b. Takmak için foo2, bunun yerine ilk açıkça (adında olmayan geçici çifti nesnesini oluşturan pairdenir), Fooüzerinde 'in kopya kurucu foo2(yaratma foo8bir iç üyesi olarak pair). Daha sonra insert()bu çifti unordered_mapdüzenledik foo8, bu da kendi dahili kopyasını ( foo9) oluşturmak için kopya kurucuyu tekrar çağırmaya (açık ) neden oldu . Gibi foos 0 ve 1, sonuçta tek fark bu sokulması için iki yıkıcı aramaları yapıldı foo8biz sonuna ulaştığı zaman bireyin yıkıcı sadece denilen main()yerine hemen sonra çağrılan insert()bitmiş.
Kullanmak foo3sadece 1 kopyala / taşı yapıcı çağrısıyla ( foo10dahili olarak içinde oluşturma unordered_map) ve sadece 1 Foo'in yıkıcı çağrısıyla sonuçlandı. (Buna daha sonra geri döneceğim).
İçin foo11, doğrudan için tam sayı 11 geçirilir emplace(11, d), böylece unordered_mapçağırır Foo(int)getirilmesini, kendi içinde iken kurucu emplace()yöntemi. (2) ve (3) 'ten farklı olarak, bunu fooyapmak için önceden var olan bir nesneye bile ihtiyacımız yoktu . Önemlisi, bir kurucuya yalnızca 1 çağrının Foogerçekleştiğine (ki bu yaratıldı foo11) dikkat edin.
Daha sonra, 12 tamsayısını doğrudan geçtik insert({12, d}). Bunun tersine emplace(11, d)(bir hatırlatıcı sadece bir Fookurucu çağrısıyla insert({12, d})sonuçlandı ), bu çağrı Foo'yapıcısına (yaratma foo12ve foo13) iki çağrıyla sonuçlandı .
Arasındaki ana "büyük resmi" ne fark Bu gösteriler insert()ve emplace()geçerli:
insert() Neredeyse her zaman kullanmak, her zaman bir kurucuya yapılan herhangi bir çağrı tamamen dahili olarak (yani, metod tanımının kapsamı dahilinde ) yapılırsa, kapsamı içinde bir Foonesnenin inşasını veya varlığını gerektirir main()(ardından bir kopyala veya taşıdır). İletdiğiniz anahtarın bağımsız değişkenleri doğrudan tanımlayıcı içindeki bir yapıcı çağrısına yönlendirilir (isteğe bağlı ek ayrıntılar: burada yeni oluşturulan nesne hemen üye değişkenlerinden birine dahil edilir, böylece hiçbir yıkıcı çağrılmaz. yürütme bırakır ve taşıma veya kopyalama yapıcıları çağrılmaz).emplace()Foounordered_mapemplace()emplace()Foounordered_map::emplace()unordered_mapemplace()
Not: Yukarıdaki " neredeyse " in " hemen hemen her zaman " ın nedeni aşağıda I) 'de açıklanmaktadır.
- devam etti: görüşmesi özelliğini nedeni
umap.emplace(foo3, d)olarak adlandırılan Foo'ın const olmayan kopya yapıcısı şudur: Kullandığımız bu yana emplace(), derleyici olduğunu bilir foo3(bir const olmayan Foobir nesne) bazı bir argüman olması gerekiyordu Fooyapıcısı. Bu durumda, en uygun Fookurucu, sabit olmayan kopya kurucusudur Foo(Foo& f2). Bu yüzden umap.emplace(foo3, d)kopya oluşturucu olarak adlandırılmadı umap.emplace(11, d).
Son Söz:
I. Bir aşırı yükün insert()aslında eşdeğer olduğuna dikkat edin emplace() . Bu cppreference.com sayfasında açıklandığı gibi , bu cppreference.com sayfasının aşırı template<class P> std::pair<iterator, bool> insert(P&& value)yüklenmesi (2'nin aşırı yüklenmesi (2) insert()) ile eşdeğerdir emplace(std::forward<P>(value)).
II. Buradan nereye gidilir?
a. Yukarıdaki kaynak kodu ile oynayın ve çevrimiçi bulunan insert()(örneğin, burada ) ve emplace()(örneğin, burada ) için belgeleri inceleyin . Eclipse veya NetBeans gibi bir IDE kullanıyorsanız, IDE'nizi hangi aşırı yüklenmenin insert()veya emplace()çağrıldığını size söylemesi için kolayca alabilirsiniz (eclipse'de farenizin imlecini işlev çağrısı üzerinde bir saniyeliğine sabit tutun). İşte denemek için biraz kod daha:
std::cout << "\numap.insert({{" << Foo::foo_counter << ", d}})\n";
umap.insert({{Foo::foo_counter, d}});
//but umap.emplace({{Foo::foo_counter, d}}); results in a compile error!
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}));
//The above uses Foo(int) and then Foo(const Foo &), as expected. but the
// below call uses Foo(int) and the move constructor Foo(Foo&&).
//Do you see why?
std::cout << "\numap.insert(std::pair<Foo, int>({" << Foo::foo_counter << ", d}))\n";
umap.insert(std::pair<Foo, int>({Foo::foo_counter, d}));
//Not only that, but even more interesting is how the call below uses all
// three of Foo(int) and the Foo(Foo&&) move and Foo(const Foo &) copy
// constructors, despite the below call's only difference from the call above
// being the additional { }.
std::cout << "\numap.insert({std::pair<Foo, int>({" << Foo::foo_counter << ", d})})\n";
umap.insert({std::pair<Foo, int>({Foo::foo_counter, d})});
//Pay close attention to the subtle difference in the effects of the next
// two calls.
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}});
//umap.insert(std::initializer_list<std::pair<Foo, int>>({{Foo::foo_counter, d}}));
//The call below works fine, but the commented out line above gives a
// compiler error. It's instructive to find out why. The two calls
// differ by a "const".
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}}));
Yakında hangi aşırı yükleyicinin std::pair( referansa bakın ) tarafından kullanıldığını göreceksiniz unordered_map, kaç nesnenin kopyalandığı, taşındığı, yaratıldığı ve / veya yok edildiği ve bunların hepsinin ne zaman meydana geldiği üzerinde önemli bir etkisi olabilir.
b. Bunun yerine başka bir kapsayıcı sınıfı (ör. std::setVeya std::unordered_multiset) kullandığınızda ne olacağını görün std::unordered_map.
c. Şimdi, a türü yerine bir Goonesne (yalnızca yeniden adlandırılmış bir kopyası Foo) kullanın (örneğin, yerine kullanın ) ve kaç ve hangi kurucuların çağrıldığını görün. (Spoiler: bir etkisi var ama çok dramatik değil.)intunordered_mapunordered_map<Foo, Goo>unordered_map<Foo, int>Goo