Abseil Tip 65 제자리에 넣기

한글 번역


title: “Tip of the Week #65: 제자리에 요소 넣기(Putting Things in their Place)”
layout: tips
sidenav: side-nav-tips.html
published: true
permalink: tips/65
type: markdown
order: “065”

원래 게시 날짜: 2013-12-12, totw/65
작성자: Hyrum Wright (hyrum@hyrumwright.org)

“설명하죠. 아니요, 너무 길어요. 요약하겠습니다.” — 이니고 몬토야


C++11은 표준 컨테이너에 요소를 삽입하는 새로운 방법을 도입했습니다: 바로 emplace() 계열의 메서드입니다. 이 메서드는 컨테이너 내부에서 객체를 직접 생성합니다. 즉, 임시 객체를 생성한 후 이를 복사하거나 이동하여 컨테이너에 삽입하는 기존 방식과는 다릅니다. 이러한 복사를 피하는 것은 거의 모든 객체에서 더 효율적이며, 특히 std::unique_ptr과 같은 이동 전용(move-only) 객체를 표준 컨테이너에 저장하기 쉽게 만듭니다.


이전 방식과 새로운 방식 비교

벡터를 사용한 간단한 예제를 통해 두 스타일의 차이를 살펴보겠습니다. 먼저 C++11 이전의 코드를 보겠습니다:

class Foo {
 public:
  Foo(int x, int y);
  
};

void addFoo() {
  std::vector<Foo> v1;
  v1.push_back(Foo(1, 2));
}

push_back() 메서드를 사용하면 Foo 객체가 두 번 생성됩니다: 하나는 임시 인자로, 다른 하나는 임시 객체에서 이동 생성된 벡터 내의 객체입니다.

C++11의 emplace_back()을 사용하면 하나의 객체만 생성됩니다. 이는 벡터 메모리 내부에서 직접 생성됩니다. “emplace” 계열의 함수는 인자를 전달받아 내부 객체의 생성자로 전달하므로, 임시 Foo 객체를 생성할 필요가 없습니다:

void addBetterFoo() {
  std::vector<Foo> v2;
  v2.emplace_back(1, 2);
}

이동 전용 타입을 위한 emplace 메서드 활용

emplace 메서드는 성능을 향상시킬 뿐만 아니라, 이전에는 불가능했던 작업도 가능하게 만듭니다. 예를 들어, std::unique_ptr과 같은 이동 전용 타입을 컨테이너에 저장하는 경우를 생각해봅시다:

std::vector<std::unique_ptr<Foo>> v1;

이 벡터에 값을 삽입하려면 어떻게 해야 할까요? 한 가지 방법은 push_back()을 사용하여 인자로 직접 값을 생성하는 것입니다:

v1.push_back(std::unique_ptr<Foo>(new Foo(1, 2)));

이 구문은 동작하지만, 약간 복잡해 보일 수 있습니다. 불행히도 혼란을 줄이기 위해 사용되던 전통적인 방식은 더 복잡합니다:

Foo *f2 = new Foo(1, 2);
v1.push_back(std::unique_ptr<Foo>(f2));

이 코드는 컴파일되지만, 원시 포인터의 소유권이 삽입 이전까지 불명확합니다. 더 나쁜 점은 벡터가 이제 객체를 소유하게 되었지만, f2는 여전히 유효하여 나중에 실수로 삭제될 가능성이 있습니다. 특히 생성과 삽입이 위와 같이 순차적으로 이루어지지 않을 경우, 이러한 소유권 패턴은 혼란을 야기할 수 있습니다.

다른 방식은 아예 컴파일되지 않습니다. 왜냐하면 unique_ptr은 복사할 수 없기 때문입니다:

std::unique_ptr<Foo> f(new Foo(1, 2));
v1.push_back(f);             // 컴파일되지 않음!
v1.push_back(new Foo(1, 2)); // 컴파일되지 않음!

emplace 메서드를 사용하면 객체를 생성하면서 삽입하는 과정이 더 직관적이 됩니다. 다른 경우에는 unique_ptr을 벡터로 이동해야 하는 경우가 있을 수 있습니다:

std::unique_ptr<Foo> f(new Foo(1, 2));
v1.emplace_back(new Foo(1, 2));
v1.push_back(std::move(f));

emplace를 표준 반복자와 결합하면 벡터의 임의 위치에 객체를 삽입할 수도 있습니다:

v1.emplace(v1.begin(), new Foo(1, 2));

그러나 실용적인 측면에서 unique_ptr을 생성하는 이러한 방식은 피해야 합니다. C++14에서는 std::make_unique를, C++11에서는 absl::make_unique를 사용하십시오:

v1.emplace_back(std::make_unique<Foo>(1, 2));

결론

이번 Tip에서는 벡터를 예로 들었지만, emplace 메서드는 맵, 리스트 및 다른 STL 컨테이너에서도 사용할 수 있습니다.
unique_ptr과 결합하면 힙에 할당된 객체의 소유권 의미를 명확히 하면서 캡슐화를 잘 유지할 수 있습니다.

이 글이 새로운 emplace 계열 메서드의 강력함을 느끼게 하고, 적절한 상황에서 이를 활용하고자 하는 동기를 제공하길 바랍니다.