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


232

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

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

답변:


162

나는 지난 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로 업그레이드하는 것으로 간주되었을 것입니다. 대신, 나는 그 버그를 가려서 몇 달 후까지는 찾지 못했습니다.


예제에서 정확히 무엇을 구현하는지, 왜 잘못되었는지 자세히 설명하면 도움이 될 것입니다.
eddi

4
@ eddi : 이것을 설명하는 섹션을 추가했습니다 : std::unique_ptr<T>에서 명시 적 생성자가 있습니다 T *. emplace_back명시 적 생성자를 호출 할 수 있기 때문에 비 소유 포인터를 전달하면 정상적으로 컴파일됩니다. 그러나 v범위를 벗어나면 소멸자는 delete해당 포인터 를 호출하려고 시도 합니다. 포인터 new는 스택 객체이기 때문에 할당되지 않았습니다 . 이로 인해 정의되지 않은 동작이 발생합니다.
David Stone

게시 해 주셔서 감사합니다. 나는 대답을 쓸 때 그것에 대해 몰랐지만 지금은 나중에 그것을 배울 때 내가 직접 작성했으면 좋겠다. :) 새로운 기능으로 전환하는 사람들을 때려서 가장 좋은 일을하기 위해 정말로 때리고 싶습니다. . 여러분, 사람들은 C ++ 11 이전에 C ++을 사용 하고 있었지만 모든 것이 문제가 되지는 않았습니다 . 기능을 사용하는 이유를 모르는 경우 사용 하지 마십시오 . 당신이 이것을 게시하게 된 것을 기쁘게 생각합니다. +1
541686

1
@CaptainJacksparrow : 내가 의미하는 것은 암시적이고 명시 적입니다. 어느 부분이 혼란 스러웠습니까?
David Stone

4
@CaptainJacksparrow : explicit생성자는 키워드가 explicit적용된 생성자입니다 . "암시 적"생성자는 해당 키워드가없는 생성자입니다. std::unique_ptr의 생성자 의 경우 T *구현자는 std::unique_ptr해당 생성자 를 작성했지만 여기서 문제는 해당 유형의 사용자가 emplace_back이라는 명시 적 생성자를 호출 한 것입니다. 만약 push_back그 생성자를 호출하는 대신, 암시 적 생성자 만 호출 할 수있는 암시 적 변환에 의존했을 것입니다.
David Stone

117

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

struct aggregate {
    int foo;
    int bar;
};

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

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


58
이는 집계 초기화 및 이니셜 라이저 목록 초기화에만 적용됩니다. {}구문을 사용하여 실제 생성자를 호출 하려는 경우의를 제거하고을 {}사용할 수 있습니다 emplace_back.
Nicol Bolas 2016 년

바보 같은 질문 시간 : 그래서 구조체 벡터에 emplace_back을 전혀 사용할 수 없습니까? 아니면 리터럴 {42,121}을 사용하여이 스타일에 적합하지 않습니까?
Phil H

1
@LucDanton : 내가 말했듯이, 집계이니셜 라이저 목록 초기화 에만 적용됩니다 . {}구문을 사용 하여 실제 생성자를 호출 할 수 있습니다 . aggregate정수를 2 개받는 생성자를 제공 할 수 있으며이 생성자는 {}구문을 사용할 때 호출됩니다 . 요점은 생성자를 호출 하려는 경우 emplace_back생성자를 내부에서 호출하기 때문에 바람직합니다. 따라서 유형을 복사 할 필요가 없습니다.
Nicol Bolas 2016 년

11
이것은 표준의 결함으로 간주되어 해결되었습니다. 참조 cplusplus.github.io/LWG/lwg-active.html#2089
데이비드 스톤

3
@DavidStone 문제가 해결되었지만 여전히 "활성"목록에 있지 않을 것입니다 ... 아니요? 아직 해결되지 않은 문제로 보입니다. , 향했다 최신 업데이트 " [2018년 8월 23일 바타 비아 문제 처리] ", "그 말한다 P0960은 . (현재 비행)이 문제를 해결해야한다 "그리고 난 여전히 컴파일 코드를 할 수없는 것과 시도 emplace명시 적으로 상용구 생성자를 작성하지 않고도 집계 . 결함으로 취급되어 백 포트에 적합한 지 또는 C ++ <20의 사용자가 SoL을 유지할지 여부는 아직 확실하지 않습니다.
underscore_d

80

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


21
그것은 C ++의 저주 인 것 같습니다. 우리는 각각의 새로운 릴리스마다 수많은 멋진 기능을 얻었지만 많은 회사들이 호환성을 위해 일부 이전 버전을 사용하거나 특정 기능의 사용을 권장하지 않습니다 (허용하지 않는 경우).
Dan Albert

6
@Mehrdad : 좋은 시간을 가질 수있을 때 왜 충분하게 정착해야합니까? 충분하더라도 blub 에서 프로그래밍하고 싶지는 않을 것 입니다. 특히이 예제에서는 그렇지 않지만 호환성을 위해 C89로 프로그래밍하는 데 대부분의 시간을 소비하는 사람에게는 분명히 문제가됩니다.
Dan Albert

3
나는 이것이 실제로 질문에 대한 대답이라고 생각하지 않습니다. 나에게 그는 push_back바람직한 사용 사례를 요구하고 있습니다 .
Mr. Boy

3
@ Mr.Boy : C ++ 11 이전의 컴파일러와 역 호환성을 원할 때 선호됩니다. 내 대답에 분명하지 않습니까?
user541686

6
이것은 내가 예상했던 것보다 더 많은 관심을 얻었으므로 이것을 읽는 모든 사람들에게 : emplace_back은 "좋은"버전 이 아닙니다push_back . 잠재적으로 위험한 버전입니다. 다른 답변을 읽으십시오.
user541686

68

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이 경우 어떻게 다를 수 있습니까?
balki

12
emplace_back ()에 대한 호출은 완벽한 전달을 사용하여 구성을 수행하므로 벡터 크기가 조정될 때까지 (v [0]이 유효하지 않은 시점까지) v [0]이 평가되지 않습니다. push_back은 새 요소를 구성하고 필요에 따라 요소를 복사 / 이동하며 v [0]은 재 할당 전에 평가됩니다.
Marc

1
@Marc : emplace_back이 범위 내의 요소에 대해서도 작동한다는 것은 표준에 의해 보장됩니다.
David Stone

1
@DavidStone : 표준에서이 동작이 보장되는 위치에 대한 참조를 제공해 주시겠습니까? 어느 쪽이든 Visual Studio 2012 및 2015는 잘못된 동작을 나타냅니다.
Marc

4
@cameino : emplace_back은 불필요한 복사를 줄이기 위해 매개 변수의 평가를 지연시키기 위해 존재합니다. 동작은 정의되지 않았거나 컴파일러 버그입니다 (표준 분석 대기 중). 최근에 Visual Studio 2015에 대해 동일한 테스트를 실행했으며 릴리스 x64에서 123,3, 릴리스 Win32에서 123,40, 디버그 x64 및 디버그 Win32에서 123, -572662307을 받았습니다.
Marc
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.