빠른 결론 : 엥간하면 push_back 쓰자
다만 내가 해당 구조에 대해서 제대로 알고 있고, emplace 가 더 효율적인 상황이라면 emplace_back이 더 효율적이니 사용을 고려해보자.
아래와 같은 class가 있다고 가정해 보자.
class myClass {
public:
int mynum;
myClass() {
mynum = 0;
cout << "default constructor" << endl;
}
myClass(int a) {
mynum = a;
cout << "constructor with " << a << endl;
}
myClass(const myClass& obj) {
mynum = obj.mynum;
cout << "copy constructor" << endl;
}
myClass& operator=(const myClass& obj) {
this->mynum = obj.mynum;
cout << "copy assign constructor" << endl;
return *this;
}
myClass(myClass&& obj) noexcept {
this->mynum = obj.mynum;
cout << "move constructor" << endl;
}
myClass& operator=(myClass&& obj) noexcept {
this->mynum = obj.mynum;
cout << "move assign constructor" << endl;
return *this;
}
~myClass() {
cout << "destructor" << endl;
}
};
무언가... 무언가를 하는 myClass 객체가 있다. 해당 객체는 본인이 어떻게 생성되었는지/ 파괴되는지를 유저에게 알려준다.
그리고 밑에 10개의 capacity를 가진, myClass를 담을 수 있는 Vector가 있다.
int main() {
std::vector<myClass> myClassVec;
myClassVec.reserve(10); // capacity 설정.
return 0;
}
해당 vector에 각각 다른 방법으로 myclass를 넣어서 결과를 확인해 보자.
1. 다른 곳에서 객체를 만들고, push back / emplace back으로 삽입.
int main() {
cout << "==============PUSH BACK================" << endl;
{
std::vector<myClass> myClassVec;
myClassVec.reserve(10); // capacity 설정.
myClass tmp(3);
cout << "push back to myClassVec" << endl;
myClassVec.push_back(tmp);
}
cout << "---------------------------------------" << endl;
cout << endl << endl;
cout << "==============EMPLACE BACK=============" << endl;
{
std::vector<myClass> myClassVec;
myClassVec.reserve(10); // capacity 설정.
myClass tmp(3);
cout << "emplace back to myClassVec" << endl;
myClassVec.emplace_back(tmp);
}
cout << "=======================================" << endl;
return 0;
}
특이사항은 없다. tmp에서 만들어진 myClass 객체가 각각의 vector에 들어갈 때 본인을 copy하여 들어간다.
그 후 tmp가 파괴되고, myClassVec이 파괴되며 안에 들어가 있던 객체도 파괴된다.
즉. 3으로 만들어진 myClass 객체가 두 개 존재한다.
2. vector에 넣을 때 바로 객체를 만들기.
int main() {
cout << "==============PUSH BACK================" << endl;
{
std::vector<myClass> myClassVec;
myClassVec.reserve(10); // capacity 설정.
cout << "push back to myClassVec" << endl;
myClassVec.push_back(myClass(4));
cout << "push back end" << endl;
}
cout << "---------------------------------------" << endl;
cout << endl << endl;
cout << "==============EMPLACE BACK=============" << endl;
{
std::vector<myClass> myClassVec;
myClassVec.reserve(10); // capacity 설정.
cout << "emplace back to myClassVec" << endl;
myClassVec.emplace_back(myClass(4));
cout << "emplace back end" << endl;
}
cout << "=======================================" << endl;
return 0;
}
각 push back, emplace back 안에서 myClass라는 임시 객체가 만들어지고, 해당 객체가 myClassVec 안으로 이동한다.
임시 객체는 본인의 역할을 끝냈으니 사라지게 되고, 역시 중괄호 밖으로 나가면 vector가 사라지면서 다시 한 번 소멸자가 불리게 된다.
- 아니 그럼 emplace는 왜 있는거임?
-> 다음 경우를 보자.
3. myClass 생성에 필요한 인자를 전달하여 생성
int main() {
cout << "==============PUSH BACK================" << endl;
{
std::vector<myClass> myClassVec;
myClassVec.reserve(10); // capacity 설정.
cout << "push back to myClassVec" << endl;
myClassVec.push_back(4);
cout << "push back end" << endl;
}
cout << "---------------------------------------" << endl;
cout << endl << endl;
cout << "==============EMPLACE BACK=============" << endl;
{
std::vector<myClass> myClassVec;
myClassVec.reserve(10); // capacity 설정.
cout << "emplace back to myClassVec" << endl;
myClassVec.emplace_back(4);
cout << "emplace back end" << endl;
}
cout << "=======================================" << endl;
return 0;
}
뭔가 이상한 걸 넣는다.
분명히 myClassVec은 MyClass를 담는 객체인데 int를 넣고 있다. 가능한가?
-> 가능하다.
자세히 생각해보면...
vector<string> strvec;
strvec.push_back("someText");
strvec.emplace_back("someText");
우리는 이러한 형태를 아무런 의심 없이 사용했다.
다만 자세히 생각해 보면, "someText" 는 string 형태가 아닌, const char* 형태라는 것을 알 수 있다.
그런데 자연스럽게 push_back 통해 vector에 넣고 있었다.
왜? 해당 인자로 string을 초기화 할 수 있기 때문에.
const char* 형태로 string을 초기화 할 수 있기 때문에, vector에서는 해당 객체의 초기화에 필요한 인자를 받아 내부에서 생성하여 vector에 추가해 준다.
-> const char* 형태는 string으로 암시적 변환이 가능하다.
const char* 형태를 가지고 string 형태로 형 변환을 한다. 형 변환을 하며 가지고 있는 재료로(여기선 const char*) 형 변환을 하려는 객체를(여기선 string) 만든다.
그러니, 위의 main 코드에서는 myClass 초기화에 필요한 '4' 라는 int형 인자를 받아 vector에서 직접 그 인자를 가지고 myClass를 생성하고, vector 뒤에 넣어준다.
-> 여기선 int를 가지고 myClass로 형변환을 한다. int를 재료로 myClass 객체를 만들 수 있으니 작성.
결과를 보자.
이번엔 좀 다르게 나왔다. push_back은 소멸자가 두 개이고 emplace_back은 소멸자가 한 개이다.
이유를 보면...
push_back :
1. 받은 초기화 인자를 가지고 임시 객체를 만든다.
2. 해당 임시 객체를 vector 안에 이동시킨다.
3. 역할을 다 한 임시 객체를 delete한다.
이렇게, 기본 생성자 한 번. 이동 생성자 한 번. 소멸자 한 번 을 부르게 된다.
반면
emplace_back:
1. 받은 초기화 인자를 가지고, vector 내부에서 직접 객체를 만든다.
이렇게 기본 생성자 단 한 번만 사용하게 된다.
결국. 효율에 미친 사람들이라면 위의 방식대로 emplace_back을 사용하여 직접 인자를 넘겨주어, 생성자 두 번 부를 것을 한 번만에 해결할 수 있게 된다!
다만. 단점이 있다.
-
아래와 같은 상황.
myClass는 int 로만 초기화 할 수 있다. 이 때 저런 const char* 형태의 인자를 주면
push_back에서는 바로 불가능하다고 띄우지만
emplace_back 에서는 뭔 일인진 모르지만 아무런 불평 없이 받아들인다.
물론 빌드하면 실패하고, 왜 실패했는지 알려주긴 한다.
지금이야 테스트 프로젝트이니 어서 잘못된 건지 금방 알 수 있게지만, 큰 프로젝트를 할 수록 어디에서 삑낫는지 찾기가 힘들어질 것이다.
다른 단점으로는.. push_back / emplace_back 하다가 exception이 뜰 때 문제가 되는데. exception이 떳을 때는 push_back이 더 안전하다고 하다.
이 부분은 공부를 좀 더 해봐야것다...
*잘못된 정보는 덧글로 알려주시면 감사하겠습니다.
'C++ > STL' 카테고리의 다른 글
[C++] std::async, std::thread (1) (0) | 2023.04.09 |
---|---|
[C++17] std::filesystem::path (0) | 2023.04.04 |
[C++17 ] FOLD Expression (0) | 2023.03.29 |
[C++ String] raw String (0) | 2023.02.27 |
[C++ Container] map/unordered_map. at, []. element access 차이 (0) | 2023.02.24 |