문제를 풀다가 다른 사람 코드를 봤더니
queue를 쓰던 map을 쓰던 새로운 요소 혹은 객체를 삽입할 때
make_pair, make_tuple을 이용해 객체를 만들고 push 하는 것이 아니라
그냥 emplace 함수를 이용해 간편하게 넣는 걸 보고 간편하다고 생각되어 찾아보니
효율도 대체적으로 좋은 듯하여 따로 찾아 정리를 해본다. (안 좋은 경우도 있다고 합니다.)
1. 정의
c++ reference 에서 emplace를 찾아보니 이러한 설명이 나왔다.
- 컨테이너에 키가있는 요소가 없는 경우 지정된 인수로 구성된 컨테이너에 새 요소를 삽입한다.
- emplace를 주의해서 사용하면 불필요한 복사 또는 이동 작업을 피하면서 새로운 요소를 구성할 수 있다.
- 반복자 및 참조자가 무효화되지 않는다.
여기서 말하는 첫 번째, "컨테이너에 키가 있는 요소가 없는 경우"는 map과 같이 중복되는 원소(키 값)를 허용하지 않는 컨테이너에 한정되는 듯하다. 추가로 emplace는 영어로 "배치하다"라는 뜻으로 새로 삽입한다고 생각하면 편하다.
두 번째, "불필요한 복사 또는 이동 작업을 피하면서 새로운 요소를 구성할 수 있다"
이 부분이 가장 중요한데, 보통 2개 이상의 요소들을 묶어서 queue나 vector에 옮기기 전에
pair나 tuple을 만들고, 요소들을 넣고, push하는 과정이 필요하다.(늘어놓고 보니 복잡한 거 같다.) 물론 그 과정도 간단하게 코드로 구현 가능하지만,
emplace를 사용하면 사용자가 요소만 넣어줘도 사용자가 원하는 컨테이너를 찾고 넣어준다.
세 번째, "반복자 및 참조자가 무효화되지 않는다"
c++를 처음 공부할 때 책에서 나온 것 같은 냄새가 나는 문장이다.
이 문장을 레퍼런스에서 괜히 써놨을 리는 없고.. 해서 emplace와 비슷한 vector의 push_back 함수를 찾아보니 해답이 나왔다.
예를 들어 원소가 여러 개 담긴 vector가 있다고 가정해보자.
우선, iterator로 vector의 시작 원소를 가리키게 하고
이후 여러 원소들을 다시 push_back해준 후
반복자(iter)를 통해 vector의 모든 원소들을 출력하게 하면 모두 출력되지 않는다.
왜냐하면 vector의 push_back은 단지 원소를 넣을 뿐, 용량을 늘리지 않는 삽입 작업이고, 그렇기 때문에 그 위치를 지나가는 요소를 가리키는 반복자 및 포인터 (iterator)를 무효화하기 때문이다.
하지만, emplace 함수는 생성에 필요한 인자를 받아 내부에서 생성 및 삽입이 이루어지기 때문에 반복자 및 참조자가 무효화되지 않는다는 것이다.
1.1 주의 사항
원래 예시로 넘어가려다가 앞에 쓴 내용을 쓰윽 읽어보니 너무 장점만 늘어놓은 것 같아 주의 사항을 적어보자면, emplace는 호환성이 떨어진다고 한다. 이 부분을 보고 대체 어떤 부분에서 그런지
찾아봤는데.. Microsoft Visual C++ 표준 라이브러리 구현 공식 관리자의 답변이
"사실 오래전에 버그가 발생해서 올바른 버전으로 구현하지 않았다~"라고 하더라...(???)
암튼 혼자 하는 알고리즘 또는 개인 프로젝트에서는 유용하나, 다른 사람과 같이 할 때는 웬만하면 push_back, insert를 사용하는 것을 추천한다.
2. 사용 예시
대체로 emplace를 사용하는 컨테이너들은 vector, queue, map, list가 있는 듯하다.
2.1 vector - push_back vs emplace_back
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
#include <iostream>
#include <vector>
#include <tuple>
using namespace std;
int main()
{
int a, b, c;
vector<tuple<int, int, int> > v;
v.push_back(make_tuple(1, 2, 3));
tie(a, b, c) = v[0];
printf("%d %d %d\n", a, b, c); //1 2 3
v.emplace_back(4, 5, 6);
tie(a, b, c) = v[1];
printf("%d %d %d\n", a, b, c); // 4 5 6
}
|
cs |
둘의 결과는 동일하다.
2.2 queue - push vs emplace
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
#include <iostream>
#include <tuple>
#include <queue>
using namespace std;
int main()
{
int a, b, c;
queue<tuple<int, int, int> > q;
q.push(make_tuple(1, 2, 3));
tie(a, b, c) = q.front();
printf("%d %d %d\n", a, b, c);
q.pop();
q.emplace(4, 5, 6);
tie(a, b, c) = q.front();
printf("%d %d %d\n", a, b, c);
}
|
cs |
위 결과와 같다.
2.3 map - insert + make_pair vs emplace
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
#include <iostream>
#include <tuple>
#include <map>
using namespace std;
int main()
{
map<int, int> m;
m.insert(make_pair(1, 2));
m.emplace(3, 4);
for (auto it = m.begin(); it != m.end(); it++) {
cout << it->first << " " << it->second << "\n";
}
}
|
cs |
2.4 list - insert vs emplace
1
2
3
4
5
6
7
8
9
10
11
12
13
|
#include <iostream>
#include <list>
using namespace std;
int main()
{
list<pair<int, int> > L;
L.insert(L.begin(), make_pair(1, 2));
L.emplace(L.begin(), 3, 4);
for (auto it = L.rbegin(); it != L.rend(); it++) {
cout << it->first << " " << it->second << "\n";
}
}
|
cs |
3. 마무리
앞으로도 계속 이 함수를 쓸지는 잘 모르겠지만
알고리즘 문제를 풀 때, 특히 tuple을 container에 넣을 때는 emplace 함수를 쓰게 될 것 같다.
다른 분들도 한 번씩 써보는 걸 추천해본다.
'algorithm class' 카테고리의 다른 글
알고리즘(Algorithm)이란? (0) | 2020.06.07 |
---|---|
[자료구조] 트라이(Trie) (0) | 2020.05.06 |
LCS 알고리즘 (0) | 2020.02.12 |
위상 정렬 (0) | 2020.02.12 |
유니온 파인드(Disjoint-set) 이란? (0) | 2020.01.06 |