IT story

왜 emplace_back 대신 push_back을 사용합니까?

hot-time 2020. 5. 1. 08:09
반응형

왜 emplace_back 대신 push_back을 사용합니까?


C ++ 11 벡터에는 새로운 기능이 emplace_back있습니다. push_back복사를 피하기 위해 컴파일러 최적화에 의존하는와 달리 , emplace_back완벽한 전달을 사용하여 인수를 생성자에게 직접 보내어 객체를 제자리에 만듭니다. emplace_back모든 것을 push_back할 수있는 것은 나에게 보이지만 , 때로는 더 잘 할 것입니다 (그러나 결코 더 나쁘지는 않습니다).

어떤 이유를 사용해야 push_back합니까?


나는 지난 4 년 동안이 질문에 대해 상당히 생각했습니다. 나는 push_back에 대한 대부분의 설명 emplace_back이 전체 그림을 그리워 한다는 결론에 도달했습니다 .

작년에 저는 C ++ 14의 C ++ Now에서 Type Deduction에 관한 프레젠테이션을했습니다 . 13:49 push_backvs. 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것은 암시적인 생성자 만 호출한다는 것입니다. 암시 적 생성자는 안전해야합니다. 당신이 암시를 구성 할 수있는 경우 UA로부터 T, 당신은 그 말 U에 모든 정보를 저장할 수있는 T손실없이. 거의 모든 상황에서 통과하는 것이 안전 T하며 U대신에 아무도 신경 쓰지 않을 것입니다. 암시 적 생성자의 좋은 예는에서로 변환하는 std::uint32_tstd::uint64_t입니다. 암시 적 변환의 나쁜 예는 doublestd::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 로의 업그레이드 성공으로 간주되었을 것입니다. 대신, 나는 그 버그를 가려서 몇 달 후까지는 찾지 못했습니다.


push_back항상 내가 좋아하는 균일 한 초기화를 사용할 수 있습니다. 예를 들어 :

struct aggregate {
    int foo;
    int bar;
};

std::vector<aggregate> v;
v.push_back({ 42, 121 });

반면에 v.emplace_back({ 42, 121 });작동하지 않습니다.


C ++ 11 이전 컴파일러와의 호환성


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]);

Note: The use of a vector of ints is for demonstration purposes. I discovered this issue with a much more complex class which included dynamically allocated member variables and the call to emplace_back() resulted in a hard crash.

참고URL : https://stackoverflow.com/questions/10890653/why-would-i-ever-use-push-back-instead-of-emplace-back

반응형