일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 알고리즘
- DP
- 너비우선탐색
- 정석학술정보관
- OS
- 코딩
- 컴공과
- Computer science
- cs
- Stack
- bfs
- coding
- 오퍼레이팅시스템
- 백준
- 구현
- 문제풀이
- 개발
- Operating System
- 그래프
- 스택
- 브루트포스
- 컴퓨터공학과
- 북리뷰
- c++
- 정석
- 코테
- 오에스
- 자료구조
- 컴공
- vector
- Today
- Total
Little Jay
[C++] emplace_back()과 push_back(), 그리고 Clang-Tidy Bug 본문
vector container을 사용할 때 대부분 push_back()을 사용한다.
직관적으로 vector 컨테이너에 원소를 위에 삽입한다라는 의미이다.
그러나 push_back()함수에는 문제점이 존재한다.
push_back() 함수는 '객체'를 집어넣는 형태이므로, 원형은 아래와 같다.
void push_back (const value_type& val);
void push_back (value_type&& val);
emplace_back()도 동일하게 컨테이너 마지막에 '객체'를 삽입하는 기능을 수행한다.
push_back()쪽을 먼저 본다면 push_back()은 rvalue, 즉 임시객체가 필요하다.
그렇기 때문에 push_back()이 어떻게 동작을 하나면, 인자로 필요한 객체를 생성 후 push_back() 함수 내부에서 다시 한번 복사가 일어난 뒤 push_back이 끝날 때 인자들과 객체가 파괴된다.
즉 객체를 하나 추가할 때 쓸데없이 2번 복사하고 파괴한다.
emplace_back 함수는 가변인자 템플릿을 사용하여 객체 생성에 필요한 인자만 받은 후,
함수 내에서 객체를 생성해 삽입하는 방식이다.
원형은 아래와 같다.
template< class... Args > //(since C++11)
void emplace_back( Args&&... args ); //(until C++17)
template< class... Args > //(since C++17)
reference emplace_back( Args&&... args ); //(until C++20)
template< class... Args >
constexpr reference emplace_back( Args&&... args ); //(since C++20)
push_back과 같은 삽입 함수들은 삽입할 객체를 받지만,
emplace_back과 같은 생성 삽입 함수는 삽입할 객체의 생성자를 위한 인자들을 받아
std::vector 내에서 직접 객체를 생성하여 삽입하므로 임시 객체의 생성과 파괴, 복사(혹은 move)를 하지 않아도 되어
성능상 더 유리 할 수 있다.
(코드 출처: https://openmynotepad.tistory.com/10)
#include <iostream>
#include <vector>
#define endl '\n'
using namespace std;
class myVec {
public:
myVec(const int _n) : m_nx(_n) { cout << "일반 생성자 호출" << endl; }
myVec(const myVec& rhs) : m_nx(rhs.m_nx) { cout << "복사 생성자 호출" << endl; }
myVec(const myVec&& rhs) : m_nx(std::move(rhs.m_nx)) { cout << "이동 생성자 호출" << endl; }
~myVec() { cout << "소멸자 호출" << endl; }
private:
int m_nx;
};
int main() {
std::vector<myVec> v;
cout << "push_back 호출" << endl;
v.push_back(myVec(3));
cout << "emplace_back 호출" << endl;
v.emplace_back(3);
return 0;
}
위의 코드를 push_back()과 emplace_back()을 각각 주석 처리해서 실행시켜보면
push_back()에서는 이동 생성자가 호출되었고, 소멸자도 총 두번 실행된다.
반면에 emplace_back() 같은 경우에는 이동 생성자가 호출되지 않으며, 소멸자도 한번 실행 된다.
이래서 얼핏 보면 emplace_back()이 push_back()보다 성능이 좋으니까 이걸 써야겠다고 생각할 수 있다.
물론 emplace_back()을 사용하지 말라는 것이 아니다.
분명 이 함수도 단점이 존재하기에 이 함수의 동작 원리를 알고 사용하면 좋을 것 같다.
먼저 emplace_back()은 가변인자 템플릿을 사용한다. 원형을 보아도 쉽게 알 수 있는 부분이다.
그러기 때문에 아래와 같은 문제가 발생 할 수 있다.
#include <vector>
class A {
public:
explicit A(int /*unused*/) {}
};
int main() {
double foo = 4.5;
std::vector<A> a_vec{};
a_vec.emplace_back(foo); // No warning with Wconversion
//A a(foo); // Gives compiler warning with Wconversion as expected
}
explict 명령어로 형변환을 막았음에도 불구하고 emplace_back()은 이를 무시하고 그냥 형변환을 진행시켜버린다.
이 문제점은 살짝 critical할 수 있는데, 컴파일러가 이를 컴파일 시에 에러를 못잡아 준다는 것이다.
이것이 우리가 의도한 것이 아니라면 명시적인 push_back()을 고려하는 것이 더 좋을 것이다.
또한 다른 문제점은 앞에서 봤던 소멸자가 없다는 것이다.
emplace_back()의 인자로 포인터 형식이 들어왔으면 생성자는 호출이 되지만 소멸자가 호출되지는 않아 memory를 delete하지 않을 수 있다. 이는 당연하게도 memory leak와도 연결된다.
정리하자면 emplace_back()은 안전하지 않은 코드이다.
성능적으로는 push_back()보다는 좋지만 이러한 리스크들을 안고 emplace_back()을 사용하기에는 부담이 되는 것은 부정할 수 없는 사실인 것 같다.
내가 emplace_back()을 사용하는 경우는 거의 백준이나 프로그래머스에서 vector 컨테이너를 사용할 때 주로 사용하고 나머지는 거의 push_back을 사용하는 경향이다. (이거 때문에 자료구조 실습 수업 할때 한번 데인적이 있다)
그래서 emplace_back()은 양날의 검임을 알고 조심히 사용하자.
이 글을 쓴 이유는 Clion IDE의 Clang-Tidy의 버그가 존재하기 때문이다.
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
template<typename T>
vector<T> merge(vector<T>& arr1, vector<T>& arr2) {
vector<T> merged;
auto iter1 = arr1.begin();
auto iter2 = arr2.begin();
while(iter1 != arr1.end() && iter2 != arr2.end()) {
if (*iter1 < *iter2) {
merged.template emplace_back(*iter1);
iter1++;
}
else {
merged.template emplace_back(*iter2);
iter2++;
}
}
if (iter1 != arr1.end()) {
for (; iter1 != arr1.end(); iter1++)
merged.template emplace_back(*iter1);
}
else {
for (; iter2 != arr2.end(); iter2++)
merged.template emplace_back(*iter2);
}
return merged;
}
template<typename T>
vector<T> merge_sort(vector<T> arr) {
if (arr.size() > 1) {
auto mid = size_t(arr.size() / 2);
auto left = merge_sort<T>(vector<T>(arr.begin(), arr.begin() + mid));
auto right = merge_sort<T>(vector<T>(arr.begin() + mid, arr.end()));
return merge<T>(left, right);
}
return arr;
}
병합정렬(merge sort)를 구현한 코드이다.
이 코드를 보게 되면 이상한 것이 있는데 바로 emplace_back()을 하는 부분이다.
일반적으로 벡터의 삽입 함수는 vector.push_back(), vector.emplace_back() 을 하면 정상적으로 동작한다.
그러나 CLion의 Clang-tidy는 내가 emplace_back()을 할 때 코드 자동완성을 저렇게 시켜주었다.
심지어 저렇게 이상한 코드도 잘 동작을 한다.
위의 코드를 repl.it 에서도 돌려보았지만 문제없이 컴파일 되었다.
Clang을 사용하는 IDE에서는 이 코드들이 정상적으로 돌아간다.
이 부분이 이상하게 여겨 stackoverflow에 질문을 했고 돌아온 답변은 CLion의 Clang-tidy에 버그가 있다는 답변이었다.
C++, 'template' keyword before emplace_back(), how does it works?
I was studying Algorithm in C++ using CLion. I'm currently using Windows 10, Clion 2021.3.3 with bundled MingW I was working on code, Merge Sort algorithm, and I had chance to use vector.emplace_ba...
stackoverflow.com
원래 vector.template emplace_back<>()은 허용이 된다.
내가 정의한 벡터와 emplace_back()이 가변인자 템플릿을 사용하기 때문에
여기에 자료형을 명시적 혹은 암시적으로 써줘도 되기 때문이다.
그러나 일부 컴파일러들은 이 규칙을 적용하는 것이 매우 느슨하기 때문에
템플릿 접두사가 있는 버전도 받아들이지만 이는 명백히 기술적으로 잘못된 것이다.
혹시나 CLion을 쓰다가 나처럼 에러가 발생한 사람들이 궁금해 할까봐 글을 남기게 되었다.
Reference
https://openmynotepad.tistory.com/10
https://gumeo.github.io/post/emplace-back/
https://sonagi87174.tistory.com/14
https://blog.naver.com/sorkelf/220825930008
'Univ > Study' 카테고리의 다른 글
[OS] pthread.h 실행 명령어 (0) | 2022.05.10 |
---|---|
객체 지향의 원칙들(SOLID) (0) | 2022.03.04 |
[C++] for 문을 사용할때 조건식에서 주의해야 할 점(unsigned와 size_type 그리고 overflow) (0) | 2022.02.17 |
[C++] Attributes (0) | 2022.02.03 |
Objected-Oriented Programming의 기둥 (0) | 2022.01.27 |