次のコードは、との「全体像」がどのようにinsert()
異なるかを理解するのに役立ちます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();
}
私が得た出力は:
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
の1への2つの呼び出しに必要なFoo
のコピー/移動コンストラクタとの2つの呼び出しFoo
の(私は今説明するように)デストラクタを:
a。それぞれを挿入するfoo0
し、foo1
(一時的なオブジェクト作成foo4
及びfoo6
そのデストラクタ挿入が完了した後、直ちに呼び出されたそれぞれ)。さらに、unordered_mapの内部Foo
s(Foo
5と7)も、unordered_mapが破棄されたときにデストラクタが呼び出されました。
b。を挿入するためにfoo2
、代わりに最初に非一時的なペアオブジェクト(と呼ばれるpair
)を明示的に作成しました。これは、Foo
のコピーコンストラクターを呼び出します(の内部メンバーとしてfoo2
作成)。次に、このペアを編集しました。その結果、コピーコンストラクターが再び呼び出され(on )、独自の内部コピー(foo8
pair
insert()
unordered_map
foo8
foo9
)。foo
s 0および1の場合と同様に、最終結果はこの挿入に対する2つのデストラクタ呼び出しでしたが、唯一の違いは、foo8
デストラクタが、終了main()
直後に呼び出されるのではなく、最後に到達したときにのみ呼び出されることinsert()
です。
強調 foo3
により、コピー/移動コンストラクター呼び出し(foo10
内部で作成unordered_map
)が1つだけ、Foo
のデストラクタが1つだけ呼び出されました。(後でこれに戻ります)。
の場合foo11
、整数11を直接渡したemplace(11, d)
ので、メソッドの実行中にコンストラクターunordered_map
が呼び出されます。(2)と(3)とは異なり、これを行うためにいくつかの既存のオブジェクトは必要ありませんでした。重要なことに、コンストラクターへの呼び出しが1つだけ発生したことに注意してください(これによりが作成されました)。Foo(int)
emplace()
foo
Foo
foo11
次に、整数12をに直接渡しましたinsert({12, d})
。とは異なりemplace(11, d)
(リコールの結果、Foo
コンストラクターが1回だけ呼び出された)、このへの呼び出しinsert({12, d})
により、Foo
のコンストラクターが2回呼び出された(作成foo12
とfoo13
)。
このショー何の間の主な「全体像」の違いinsert()
とemplace()
次のとおりです。
insert()
ほとんどの場合、を使用するには、のスコープ内のFoo
オブジェクトの作成または存在main()
(その後にコピーまたは移動)が必要ですが、使用する場合emplace()
、Foo
コンストラクターへの呼び出しは完全に内部でunordered_map
(つまり、emplace()
メソッドの定義のスコープ内で)行われます。渡すキーの引数は、内部のコンストラクター呼び出しにemplace()
直接転送されFoo
ますunordered_map::emplace()
の定義のます(オプションの追加詳細:この新しく構築されたオブジェクトは、unordered_map
のメンバー変数の1つにすぐに組み込まれるため、デストラクタが呼び出されないようにします実行は終了emplace()
し、移動またはコピーコンストラクターは呼び出されません)。
注:「ほぼ」の理由」「で、ほとんどの場合、以下の上記Iで説明されます」)。
- 続き:
umap.emplace(foo3, d)
called Foo
の非constコピーコンストラクターを呼び出す理由は次のとおりです:を使用しているemplace()
ため、コンパイラーはfoo3
(非const Foo
オブジェクト)がFoo
コンストラクターの引数であることを認識しています。この場合、最も適合するFoo
コンストラクタは、非constコピーコンストラクタFoo(Foo& f2)
です。umap.emplace(foo3, d)
コピーコンストラクターが呼び出されumap.emplace(11, d)
なかったのはこのためです。
エピローグ:
I.の1つのオーバーロードinsert()
は実際にはと同等 であることに注意してくださいemplace()
。このcppreference.comページで説明されているように、オーバーロードtemplate<class P> std::pair<iterator, bool> insert(P&& value)
( overload(2))insert()
はと同等emplace(std::forward<P>(value))
です。
II。ここからどこへ行く?
a。以下のための上記のソースコードとドキュメント研究で遊んでinsert()
(例えばここ)とemplace()
(例えばここ)さんがオンラインで見つけること。EclipseやNetBeansなどのIDEを使用している場合は、IDEに簡単にオーバーロードされている、insert()
またはemplace()
呼び出されていることを通知させることができます(Eclipseでは、マウスのカーソルを関数呼び出しの上で一秒間動かし続けるだけです)。以下に、試してみるコードをいくつか示します。
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}}));
std::pair
コンストラクターのオーバーロードがすぐにわかります(リファレンスを参照))が最終的に使用されるunordered_map
ようになると、コピー、移動、作成、および/または破棄されるオブジェクトの数、およびこれらすべてが発生するタイミングに重要な影響を与えるます。
b。代わりに他のコンテナクラス(std::set
またはまたはstd::unordered_multiset
)を使用するとどうなるかを確認します。std::unordered_map
。
c。ここで、の範囲タイプとしての代わりにGoo
オブジェクト(の名前を変更したのコピーFoo
)を使用し(つまり、の代わりに使用)、呼び出されるコンストラクターの数とコンストラクターを確認します。(ネタバレ:効果はありますが、それほど劇的ではありません。)int
unordered_map
unordered_map<Foo, Goo>
unordered_map<Foo, int>
Goo