티스토리 뷰

반응형

 

 

 저는 최근에 c++에서 벡터를 포함한 컨테이너를 사용할 때 데이터를 삽입할 일이 있으면 위와 같이 emplace_back(queue 종류에서는 emplace)을 많이 쓰고 있습니다. 코딩 테스트용 알고리즘을 공부하는 책에서 emplace_back이 메모리 할당을 하는 동시에 값을 삽입하기 때문에 push_back보다 속도가 더 빠르다고 하길래 그 이후로는 최적화에 조금이나마 도움이 되라고 쭉 emplace_back을 써 왔죠. 근데 쓰다 보면 emplace_back이 가능하지 않고 push_back으로만 해결되는 경우도 있어서 정확한 차이가 궁금해져서 찾아봤습니다. 

 

 시작 전에 한 줄 요약하면

 push_back은 객체를 삽입하기 위해서 똑같은 임시 객체를 하나 더 만들어서 거기에 복사한 다음 벡터에 삽입 -> 삽입이 끝나면 임시 객체 파괴 -> 잠깐 쓰고 버릴 메모리를 굳이 할당해 줘야 함. 임시 객체 생성자를 호출해서 생성하고 소멸자를 불러서 파괴시키는 과정에서 불필요한 연산이 생긴다. => 다소 비효율적

 

 emplace_back은 가변인자 템플릿을 사용해서 삽입하려는 자료형에 따라 함수 내에서 삽입을 위한 객체를 자체 생성할 수 있다 -> 즉 똑같은 임시 객체를 만들 필요가 없다 -> 파괴해야 할 임시 객체 자체가 생기지 않는다 -> 잠깐 쓰고 버릴 메모리를 할당할 필요가 없다. => push_back에 비해 효율적 

 

 

push_back(push)

 

 c++ 벡터에서 push_back은 메모리 할당을 하지 않고 데이터를 삽입하기만 합니다. 그렇기 때문에 벡터에 값을 삽입하려면 완성된 객체가 있어야 삽입하는 것이 가능하겠죠. 그래서 벡터는 push_back을 할 때 인자값과 똑같은 임시 객체를 하나 더 만들어서(이 과정에서 해당 객체를 만들기 위한 생성자 호출) 거기에 인자값으로 받은 값을 복사합니다. 그리고 벡터에 임시 객체를 추가합니다. 추가가 끝나고 push_back 함수를 빠져 나오면 push_back 용으로 만들었던 임시 객체를 삭제합니다(이 과정에서 해당 객체가 소멸되기 때문에 소멸자 호출). 어떤 클래스를 담은 벡터를 만들었을 때 새로운 클래스 객체를 만들어서 벡터에 push_back하려고 하면 인자값으로 넣은 객체와 똑같은 것이 하나 더 만들어졌다 소멸되는 것입니다. 효율을 중시하는 현대인으로서는 다소 비효율적이라고 느껴지네요. 

 

 이런 식으로 값을 계속 추가하다가 처음에 할당된 메모리보다 벡터의 사이즈가 커진다면 커진 벡터를 할당할 새 메모리 공간을 찾은 다음 거기에 새롭게 할당을 하고 기존 벡터의 데이터를 거기다 복사한 다음 기존 벡터는 파괴합니다. 음 여기까지 봐도 여전히 비효율적이네요. 그냥 뒤에다 바로바로 추가하면 안 되나?

 이 생각에서 나온 것이 emplace_back(emplace)입니다. 

 

 

emplace_back(emplace)

 

template <class _Up, class... _Args>
    _LIBCPP_DEPRECATED_IN_CXX17 _LIBCPP_INLINE_VISIBILITY
    void construct(_Up* __p, _Args&&... __args) {
        ::new ((void*)__p) _Up(_VSTD::forward<_Args>(__args)...);
    }

 

 emplace_back은 벡터에 메모리 할당을 하는 동시에 값을 삽입하는 것이 가능합니다. 가변인자 템플릿을 사용해서(위에서 보시다시피 자체 생성자 함수가 있음) 인자값을 넣어주면 그걸로 emplace_back 함수 내에서 자체적으로 객체를 생성할 수 있습니다. 즉 일반적인 클래스 객체를 만들기 위한 생성자 호출을 할 필요가 없다는 것이죵... emplace_back 함수가 알아서 하니까요. emplace_back 함수 내부에서 삽입용 객체를 만들기 위한 생성자 한 번만 호출됩니다. 그 만큼 메모리 할당을 아낄 수 있다는 말이 됩니다. 그 만큼 속도도 더 빠르겠네요. 그리고 push_back 함수와 다르게 메모리 할당도 할 수 있습니다. 

 

vector<vector<int>> vec;
vec.push_back(2); //불가능! -> No matching member function for call to 'push_back'
vec.emplace_back(2); //가능~

 

 2차원 벡터를 만들었는데 벡터의 행의 길이는 정해줄 수 있는데 열의 길이는 데이터 상황에 따라 바뀔 거 같아서 섣불리 할당을 해 주기 거시기 할 때가 있잖아요? 그런 경우에 위와 같은 코드를 작성할 수 있을 것입니다. 열의 길이를 정해 주려면 역시 메모리를 할당해 주어야 하는데 push_back에는 그러한 기능은 없기 때문에 위의 2번째 줄 코드는 에러가 납니다. 하지만 emplace_back은 가능합니다. 코드를 실행하면 2차원 벡터의 행만 할당하고 각 행의 사이즈는 0인 상태로 메모리가 할당됩니다. 

 

 여기까지만 보면 emplace_back 쓰는 것이 이득인 거 같아요. 그래서 저도 많이 썼구요. 하지만 꼭 그렇지도 않습니다. 

 

vector<vector<int>> vec;
vec.push_back({1,0}); //가능~
vec.emplace_back({1,0}); //불가능! -> No matching member function for call to 'emplace_back'

 

 이렇게 2차원 배열에 인자값으로 배열 자체를 넣어주는 경우에는 emplace_back은 사용이 안 되거든요. 위와 같이 쓰려면 push_back만 쓸 수 있더라고요... push_back은 데이터 그 자체를 삽입하니까 vec의 0번째 인덱스에 길이 2를 가진 1차원 벡터 삽입이 가능한 것 같은데 emplace_back은 왜 그런걸까요? 

 

void construct(_Up* __p, _Args&&... __args)

 

 앗 혹시 _Args&&... __args 이 부분에서 rvalue가 들어가야 하는데 배열은 주소가 있어서 lvalue가 되어서 그런 걸까요? 이 부분은 암만 찾아봐도 잘 모르겠네요 ㅠ 아시는 분이 제 글을 보신다면 댓글 부탁드려요...

 


 

 암튼 여기저기 찾아보고 제가 사용해 보면서도 느낀 것은 emplace_back이 push_back을 상당 부분 대체할 수 있긴 하지만 위의 예시처럼 대체가 불가능한 부분도 있기 때문에 다른 사람들과 함께 하는 프로젝트라면 호환성을 위해 push_back을 쓰는 것이 좋다는 생각입니다. 실제로 다른 분들 글에서도 그렇게 얘기 하구요. 개인 프로젝트나 코딩 테스트용 알고리즘 문제 풀이라면 emplace_back을 써도 전혀 문제가 없습니다. 특히 효율성을 중요시 하는 코딩 테스트에선... 

 

 좀 더 자세한 예제 코드와 설명을 보고 싶으시면 아래 블로그를 참고하세요. 

https://openmynotepad.tistory.com/10

 

emplace_back 과 push_back 의 차이

item 타입의 생성자가 타입을 인자로 받는다면? push_back 함수는 '객체' 를 집어 넣는 형식으로, 객체가 없이 삽입을 하려면 "임시객체 (rvalue) " 가 있어야 합니다. 또는 암시적 형변환이 가능하다면,

openmynotepad.tistory.com

 

반응형

'C++' 카테고리의 다른 글

[C++] find vs find_if 차이점  (0) 2021.10.31
댓글
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함