주간 팁 #123: absl::optional
과 std::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이 될 수 없습니다. 이는 잠재적인 버그를 제거합니다.
단점:
val_
의 수명은Foo
객체의 수명에 고정됩니다.- 교체 가능성:
Bar
가 이동(이사) 또는 교체 연산을 지원한다면, 기존의 참조는 여전히 유효하지만 내용은 변경할 수 있습니다.
- 교체 가능성:
Bar
생성자에 전달할 인수는 반드시Foo
생성자의 초기화 리스트에서 계산되어야 합니다. 복잡한 표현식이 필요한 경우 어렵습니다.
2. absl::optional<Bar>
로 저장
absl::optional
은 일반 객체와 std::unique_ptr
의 중간 수준의 유연성을 제공합니다.
- 객체는
Foo
내부에 저장되지만 비어 있을 수 있습니다. - 필요 시
opt_ = ...
또는opt_.emplace(...)
로 값을 할당할 수 있습니다.
주의사항:
- 비어 있는
absl::optional
도 메모리를 사용합니다. (할당된 것과 동일한 크기) - 객체가 언제 생성되고 소멸되는지 명확하지 않을 수 있습니다.
- 비어 있는 상태에서 접근하면 문제가 발생할 수 있습니다.
3. std::unique_ptr<Bar>
로 저장
가장 유연한 방법입니다.
- 객체는
Foo
외부에 저장되며,std::unique_ptr
이 비어 있을 수 있습니다. - 객체의 소유권을 이전하거나, 다른 곳에서 소유권을 받을 수 있습니다.
장점:
- 소유권 이전 및 이동 가능.
- 객체가 동적으로 생성되므로, 더 많은 제어가 가능.
단점:
- 복잡성 증가:
- 독자가 내부 구조를 이해하는 데 시간이 걸릴 수 있습니다.
- 객체 생성 및 소멸 시점이 덜 명확합니다.
- CPU 캐시 비효율성:
- 추가 간접 참조로 인해 힙 할당이 발생하고 CPU 캐시 친화도가 낮아집니다.
- 복사 불가:
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; |