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_map
zaman, dahili olarak Foo
nesneleri (ve diyelim ki Foo *
) anahtarlar olarak depolar ; bunların hepsi yok edildiğinde unordered_map
yok edilir. Burada unordered_map
iç anahtarlar foos 13, 11, 5, 10, 7 ve 9'du.
- Yani teknik olarak,
unordered_map
aslında std::pair<const Foo, int>
nesneleri depolar , bu da Foo
nesneleri 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::pair
nesnenin unordered_map
kullanımının ince ama önemli teknikler getirerek nasıl yedeklendiğini ve anlanması önemlidir.
Her takma foo0
, foo1
ve foo2
birine 2 arama gerekli Foo
bireyin kopyalama / taşıma yapıcılar ve 2 çağrı Foo
'destructor lar (şimdi açıklamak gibi):
a. Her birinin yerleştirilmesi foo0
ve imha edicinin yerleştirme tamamlandıktan hemen sonra çağrıldığı foo1
geçici bir nesne ( foo4
ve foo6
sırasıyla) oluşturması. Buna ek olarak, unordered_map'in dahili Foo
s ( Foo
s 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 pair
denir), Foo
üzerinde 'in kopya kurucu foo2
(yaratma foo8
bir iç üyesi olarak pair
). Daha sonra insert()
bu çifti unordered_map
düzenledik foo8
, bu da kendi dahili kopyasını ( foo9
) oluşturmak için kopya kurucuyu tekrar çağırmaya (açık ) neden oldu . Gibi foo
s 0 ve 1, sonuçta tek fark bu sokulması için iki yıkıcı aramaları yapıldı foo8
biz sonuna ulaştığı zaman bireyin yıkıcı sadece denilen main()
yerine hemen sonra çağrılan insert()
bitmiş.
Kullanmak foo3
sadece 1 kopyala / taşı yapıcı çağrısıyla ( foo10
dahili 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 foo
yapmak için önceden var olan bir nesneye bile ihtiyacımız yoktu . Önemlisi, bir kurucuya yalnızca 1 çağrının Foo
gerç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 Foo
kurucu çağrısıyla insert({12, d})
sonuçlandı ), bu çağrı Foo
'yapıcısına (yaratma foo12
ve 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 Foo
nesnenin 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()
Foo
unordered_map
emplace()
emplace()
Foo
unordered_map::emplace()
unordered_map
emplace()
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 Foo
bir nesne) bazı bir argüman olması gerekiyordu Foo
yapıcısı. Bu durumda, en uygun Foo
kurucu, 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::set
Veya std::unordered_multiset
) kullandığınızda ne olacağını görün std::unordered_map
.
c. Şimdi, a türü yerine bir Goo
nesne (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.)int
unordered_map
unordered_map<Foo, Goo>
unordered_map<Foo, int>
Goo