답변:
나는 지난 4 년 동안이 질문에 대해 상당히 생각했습니다. 나는 push_back
대 에 대한 대부분의 설명 emplace_back
이 전체 그림을 그리워 한다는 결론에 도달했습니다 .
작년에 저는 C ++ 14 에서 C ++ Now에서 Type Deduction에 관한 프레젠테이션을했습니다 . 13:49 에 push_back
vs. emplace_back
에 대해 이야기하기 시작 하지만 그 이전에 몇 가지 근거를 제공하는 유용한 정보가 있습니다.
실제 주요 차이점은 암시 적 생성자와 명시 적 생성자와 관련이 있습니다. 우리는 우리가 전달하고자하는 하나의 인자를 가지고있는 경우에 고려 push_back
또는 emplace_back
.
std::vector<T> v;
v.push_back(x);
v.emplace_back(x);
최적화 컴파일러가 이것에 익숙해지면 생성 된 코드 측면 에서이 두 문장 사이에 차이가 없습니다. 전통적인 지혜는 즉 push_back
다음으로 이동 얻을 것이다 임시 객체를 구성합니다 v
반면에 emplace_back
따라 인수를 전달없이 복사 또는 이동과 장소에서 직접 구성됩니다. 이것은 표준 라이브러리에 작성된 코드를 기반으로 할 수 있지만 최적화 컴파일러의 작업은 작성한 코드를 생성하는 것으로 잘못 가정합니다. 최적화 컴파일러의 작업은 실제로 플랫폼 별 최적화에 대한 전문가이고 유지 관리 성, 성능에만 관심이없는 경우 작성했을 코드를 생성하는 것입니다.
이 두 문장의 실제 차이점은 더 강력한 것은 emplace_back
모든 유형의 생성자를 호출하지만 더 신중한 push_back
것은 암시 적 생성자를 호출한다는 것입니다. 암시 적 생성자는 안전해야합니다. 당신이 암시를 구성 할 수있는 경우 U
A로부터 T
, 당신은 그 말 U
에 모든 정보를 저장할 수있는 T
손실없이. 거의 모든 상황에서 통과하는 것이 안전 T
하며 U
대신에 아무도 신경 쓰지 않을 것입니다. 암시 적 생성자의 좋은 예는에서로 변환하는 std::uint32_t
것 std::uint64_t
입니다. 암시 적 변환의 나쁜 예는 double
에 std::uint8_t
.
우리는 프로그래밍에주의를 기울이고 싶습니다. 우리는 강력한 기능을 사용하고 싶지 않습니다. 기능이 강력할수록 실수로 잘못되었거나 예기치 않은 일을하는 것이 더 쉽기 때문입니다. 명시적인 생성자를 호출하려면의 힘이 필요합니다 emplace_back
. 암시 적 생성자 만 호출하려면의 안전을 유지하십시오 push_back
.
예
std::vector<std::unique_ptr<T>> v;
T a;
v.emplace_back(std::addressof(a)); // compiles
v.push_back(std::addressof(a)); // fails to compile
std::unique_ptr<T>
의 명시 적 생성자가 있습니다 T *
. emplace_back
명시 적 생성자를 호출 할 수 있기 때문에 비 소유 포인터를 전달하면 정상적으로 컴파일됩니다. 그러나 v
범위를 벗어나면 소멸자는 delete
해당 포인터 를 호출하려고 시도 합니다. 포인터 new
는 스택 객체이기 때문에 할당되지 않았습니다 . 이로 인해 정의되지 않은 동작이 발생합니다.
이것은 단지 발명 된 코드가 아닙니다. 이것은 내가 만난 실제 생산 버그였습니다. 코드는 std::vector<T *>
이지만 내용을 소유했습니다. C ++ 11로 마이그레이션의 일환으로, 나는 제대로 변경 T *
하는 std::unique_ptr<T>
벡터가 메모리를 소유하고 있음을 나타냅니다. 그러나 나는 2012 년 이해를 바탕으로 이러한 변경 사항을 기반으로하고 있는데, 그 동안 "emplace_back은 push_back이 할 수있는 모든 작업을 수행하므로 왜 push_back을 사용합니까?"라고 생각 push_back
했습니다 emplace_back
.
대신 안전한 코드를 사용하는 것으로 코드를 그대로 두었다면 push_back
,이 오래된 버그를 즉시 포착했을 것이며 C ++ 11로 업그레이드하는 것으로 간주되었을 것입니다. 대신, 나는 그 버그를 가려서 몇 달 후까지는 찾지 못했습니다.
std::unique_ptr<T>
에서 명시 적 생성자가 있습니다 T *
. emplace_back
명시 적 생성자를 호출 할 수 있기 때문에 비 소유 포인터를 전달하면 정상적으로 컴파일됩니다. 그러나 v
범위를 벗어나면 소멸자는 delete
해당 포인터 를 호출하려고 시도 합니다. 포인터 new
는 스택 객체이기 때문에 할당되지 않았습니다 . 이로 인해 정의되지 않은 동작이 발생합니다.
explicit
생성자는 키워드가 explicit
적용된 생성자입니다 . "암시 적"생성자는 해당 키워드가없는 생성자입니다. std::unique_ptr
의 생성자 의 경우 T *
구현자는 std::unique_ptr
해당 생성자 를 작성했지만 여기서 문제는 해당 유형의 사용자가 emplace_back
이라는 명시 적 생성자를 호출 한 것입니다. 만약 push_back
그 생성자를 호출하는 대신, 암시 적 생성자 만 호출 할 수있는 암시 적 변환에 의존했을 것입니다.
push_back
항상 내가 좋아하는 균일 한 초기화를 사용할 수 있습니다. 예를 들어 :
struct aggregate {
int foo;
int bar;
};
std::vector<aggregate> v;
v.push_back({ 42, 121 });
반면에 v.emplace_back({ 42, 121 });
작동하지 않습니다.
{}
구문을 사용하여 실제 생성자를 호출 하려는 경우의를 제거하고을 {}
사용할 수 있습니다 emplace_back
.
{}
구문을 사용 하여 실제 생성자를 호출 할 수 있습니다 . aggregate
정수를 2 개받는 생성자를 제공 할 수 있으며이 생성자는 {}
구문을 사용할 때 호출됩니다 . 요점은 생성자를 호출 하려는 경우 emplace_back
생성자를 내부에서 호출하기 때문에 바람직합니다. 따라서 유형을 복사 할 필요가 없습니다.
emplace
명시 적으로 상용구 생성자를 작성하지 않고도 집계 . 결함으로 취급되어 백 포트에 적합한 지 또는 C ++ <20의 사용자가 SoL을 유지할지 여부는 아직 확실하지 않습니다.
C ++ 11 이전 컴파일러와의 호환성
push_back
바람직한 사용 사례를 요구하고 있습니다 .
emplace_back
은 "좋은"버전 이 아닙니다push_back
. 잠재적으로 위험한 버전입니다. 다른 답변을 읽으십시오.
emplace_back의 일부 라이브러리 구현은 Visual Studio 2012, 2013 및 2015와 함께 제공되는 버전을 포함하여 C ++ 표준에 지정된대로 작동하지 않습니다.
알려진 컴파일러 버그를 수용 std::vector::push_back()
하려면 매개 변수가 반복자 또는 호출 후 유효하지 않은 다른 오브젝트를 참조하는지 여부를 선호 하십시오.
std::vector<int> v;
v.emplace_back(123);
v.emplace_back(v[0]); // Produces incorrect results in some compilers
하나의 컴파일러에서 v는 예상 123 및 123 대신 123 및 21 값을 포함합니다. 이는 두 번째 호출 emplace_back
에 대한 크기 조정 v[0]
이 무효화 되기 때문입니다 .
위 코드의 실제 구현은 다음 push_back()
대신에 사용 됩니다 emplace_back()
.
std::vector<int> v;
v.emplace_back(123);
v.push_back(v[0]);
참고 : int 벡터를 사용하는 것은 데모 용입니다. 동적으로 할당 된 멤버 변수와 호출을 포함하여 훨씬 더 복잡한 클래스 로이 문제를 발견했습니다 emplace_back()
.
push_back
이 경우 어떻게 다를 수 있습니까?