다음 코드는 어떻게 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가 아닌 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
의 복사 / 이동 생성자와 2 개 통화 Foo
'소멸자의 (지금 설명대로) :
ㅏ. 각각 삽입 foo0
하고 foo1
(임시 객체 생성 foo4
하고 foo6
, 그 소멸 삽입이 완료된 후 즉시 호출 된 각각 등). 또한, unorder_map의 내부 Foo
( Foo
s 5와 7)는 unorder_map이 파괴 될 때 소멸자를 호출했습니다.
비. 삽입하기 위해 foo2
먼저 임시 쌍이 아닌 객체 ( pair
)를 명시 적으로 만들었습니다.이 객체 는의 내부 Foo
생성자로 foo2
생성 foo8
되는 의 복사 생성자 를 호출했습니다 pair
. 그런 다음 insert()
이 쌍을 수정 unordered_map
하여 사본 생성자를 다시 호출하여 (on foo8
) 자체 내부 사본 ( foo9
) 을 작성했습니다 . 와 같이 foo
S 0과 1 최종 결과는 해당되는 차이점이 삽입 두 소멸자 호출이었다 foo8
우리의 끝에 도달 할 때의 소멸자 만 불렸다 main()
보다는 후 즉시 호출되는 insert()
마쳤다.
삽입 foo3
하면 1 개의 복사 / 이동 생성자 호출 ( foo10
내부적으로 작성 unordered_map
)과 Foo
소멸자 에 대한 호출 1 개만 발생했습니다 . (나중에 다시 설명하겠습니다).
의 경우 실행이 메서드 내에있는 동안 생성자를 호출 하도록 foo11
정수 11을 직접 전달했습니다 . (2)와 (3)과는 달리, 우리는 이것을하기 위해 어떤 사전 종료 객체 도 필요하지 않았습니다 . 중요하게도 생성자 에 대한 호출이 하나만 발생했습니다 (생성됨 ).emplace(11, d)
unordered_map
Foo(int)
emplace()
foo
Foo
foo11
그런 다음 정수 12를에 직접 전달했습니다 insert({12, d})
. with emplace(11, d)
( Foo
생성자 가 한 번만 호출 된 결과) 와 달리이 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
(A const가 아닌 Foo
개체) 일부에 대한 인수로 의미 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)
( 이 cppreference.com 페이지의 과부하 (2)) insert()
는와 같습니다 emplace(std::forward<P>(value))
.
II. 여기서 어디로 가야합니까?
ㅏ. 에 대한 위의 소스 코드와 연구 문서 함께 놀러 insert()
(예 : 여기 )와 emplace()
(예 : 여기 )의 온라인으로 발견. eclipse 또는 NetBeans와 같은 IDE를 사용하는 경우 IDE에 과부하가 걸리 insert()
거나 emplace()
호출되는 것을 쉽게 알 수 있습니다 (일식에서는 마우스 커서를 잠시 동안 함수 호출 위에 안정적으로 유지하십시오). 시도해 볼 코드가 더 있습니다.
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
생성자 과부하 ( reference 참조 )가 unordered_map
얼마나 많은 객체가 복사, 이동, 생성 및 / 또는 파괴되는지뿐만 아니라이 모든 상황이 발생하는 시점에 중요한 영향을 미칠 수 있음을 곧 알게 될 것입니다 .
비. 대신 다른 컨테이너 클래스 (예 : std::set
또는 std::unordered_multiset
) 를 사용하면 어떻게되는지 확인하십시오 std::unordered_map
.
씨. 이제 범위 유형으로 대신 (대신 Goo
이름이 바뀐) 객체를 사용하고 (즉, 대신 사용 ) 생성자가 몇 개이고 어떤 생성자가 있는지 확인하십시오 . (스포일러 : 효과는 있지만 극적이지는 않습니다.)Foo
int
unordered_map
unordered_map<Foo, Goo>
unordered_map<Foo, int>
Goo