Abseil Tip 123 absl::optional과 std::unique_ptr

주간 팁 #123: absl::optionalstd::unique_ptr

원래 게시일: 2016-09-06
작성자: Alexey Sokolov (sokolov@google.com)
Etienne Dechamps (edechamps@google.com)


값을 저장하는 방법

이 팁에서는 값을 저장하는 여러 가지 방법을 다룹니다. 여기서는 클래스 멤버 변수를 예로 들지만, 아래의 많은 점은 지역 변수에도 적용됩니다.

#include <memory>
#include "absl/types/optional.h"
#include ".../bar.h"

class Foo {
  ...
 private:
  Bar val_;
  absl::optional<Bar> opt_;
  std::unique_ptr<Bar> ptr_;
};

1. 일반 객체로 저장

val_은 가장 단순한 방식입니다.

  • Foo의 생성자에서 초기화되고, 소멸자에서 해제됩니다.
  • Bar에 기본 생성자가 있으면 명시적으로 초기화하지 않아도 됩니다.

장점:

  • 안전성: val_은 항상 유효하며 null이 될 수 없습니다. 이는 잠재적인 버그를 제거합니다.

단점:

  1. val_의 수명은 Foo 객체의 수명에 고정됩니다.
    • 교체 가능성: Bar가 이동(이사) 또는 교체 연산을 지원한다면, 기존의 참조는 여전히 유효하지만 내용은 변경할 수 있습니다.
  2. Bar 생성자에 전달할 인수는 반드시 Foo 생성자의 초기화 리스트에서 계산되어야 합니다. 복잡한 표현식이 필요한 경우 어렵습니다.

2. absl::optional<Bar>로 저장

absl::optional은 일반 객체와 std::unique_ptr의 중간 수준의 유연성을 제공합니다.

  • 객체는 Foo 내부에 저장되지만 비어 있을 수 있습니다.
  • 필요 시 opt_ = ... 또는 opt_.emplace(...)로 값을 할당할 수 있습니다.

주의사항:

  1. 비어 있는 absl::optional도 메모리를 사용합니다. (할당된 것과 동일한 크기)
  2. 객체가 언제 생성되고 소멸되는지 명확하지 않을 수 있습니다.
  3. 비어 있는 상태에서 접근하면 문제가 발생할 수 있습니다.

3. std::unique_ptr<Bar>로 저장

가장 유연한 방법입니다.

  • 객체는 Foo 외부에 저장되며, std::unique_ptr이 비어 있을 수 있습니다.
  • 객체의 소유권을 이전하거나, 다른 곳에서 소유권을 받을 수 있습니다.

장점:

  1. 소유권 이전 및 이동 가능.
  2. 객체가 동적으로 생성되므로, 더 많은 제어가 가능.

단점:

  1. 복잡성 증가:
    • 독자가 내부 구조를 이해하는 데 시간이 걸릴 수 있습니다.
    • 객체 생성 및 소멸 시점이 덜 명확합니다.
  2. CPU 캐시 비효율성:
    • 추가 간접 참조로 인해 힙 할당이 발생하고 CPU 캐시 친화도가 낮아집니다.
  3. 복사 불가:
    • std::unique_ptr<Bar>는 복사할 수 없으므로 Foo도 복사할 수 없습니다.

결론

  • 가능하면 단순한 방식을 사용하세요.
    • 일반 객체를 우선 사용.
    • 필요하면 absl::optional을 시도.
    • 마지막으로 std::unique_ptr을 사용.

비교표

  Bar absl::optional<Bar> std::unique_ptr<Bar>
지연 초기화 지원  
항상 안전한 접근    
소유권 이전 지원    
Bar의 하위 클래스 저장    
이동 가능 Bar가 이동 가능할 경우 Bar가 이동 가능할 경우
복사 가능 Bar가 복사 가능할 경우 Bar가 복사 가능할 경우  
CPU 캐시 친화적  
힙 할당 없음  
메모리 사용량 sizeof(Bar) sizeof(Bar) + sizeof(bool) sizeof(Bar*) (null일 때)
객체 수명 포함하는 스코프와 동일 포함하는 스코프 내 제한 제한 없음
f(Bar*) 호출 f(&val_) f(&opt_.value()) 또는 f(&*opt_) f(ptr_.get()) 또는 f(&*ptr_)
값 제거 방법 N/A opt_.reset(); 또는 opt_ = absl::nullopt; ptr_.reset(); 또는 ptr_ = nullptr;