Suhwanc

 

문제를 풀다가 다른 사람 코드를 봤더니

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<intintint> > v;
    v.push_back(make_tuple(123));
    tie(a, b, c) = v[0];
    printf("%d %d %d\n", a, b, c); //1 2 3
 
    v.emplace_back(456);
    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<intintint> > q;
 
    q.push(make_tuple(123));
    tie(a, b, c) = q.front();
    printf("%d %d %d\n", a, b, c);
    q.pop();
 
    q.emplace(456);
    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<intint> m;
    m.insert(make_pair(12));
    m.emplace(34);
    
    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<intint> > L;
    L.insert(L.begin(), make_pair(12));
    L.emplace(L.begin(), 34);
    
    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