absl::StatusOr<T> — 값 또는 에러
한 줄 요약:
absl::StatusOr<T>는 “값 T 또는 에러 Status”를 한 type에 묶는다. 함수가 의미 있는 결과를 반환할 때 에러도 함께 표현할 수 있다. C++23의std::expected의 선구적 패턴.
#어떤 문제를 푸는가
absl::Status만으로는 값을 반환할 수 없다.
// 두 단계 패턴 — 어색User user;absl::Status s = GetUser(42, &user);if (!s.ok()) return s;// user 사용out parameter는 다음 문제를 가진다.
- 호출자가 default 생성 가능한 type만 사용 가능 —
User가 default ctor 없으면? - 에러 시 user의 상태 불명확 — 부분 초기화? 그대로?
- call site가 장황 — return type만 보고 의도를 알기 어려움.
StatusOr<T>는 결과와 에러를 함께 묶어 이 문제를 해결한다.
상태 전이를 그림으로 보면 다음과 같다.
absl::StatusOr<User> GetUser(int id);
// 호출auto result = GetUser(42);if (!result.ok()) { LOG(ERROR) << result.status(); return;}User user = *result; // 또는 std::move(*result)#기본 사용
#include "absl/status/statusor.h"
absl::StatusOr<int> ParseInt(absl::string_view s) { int v; if (!absl::SimpleAtoi(s, &v)) { return absl::InvalidArgumentError( absl::StrCat("not a number: ", s)); } return v;}
void Demo() { auto r = ParseInt("42"); if (!r.ok()) { LOG(ERROR) << r.status(); return; } int n = *r; LOG(INFO) << "parsed: " << n;}생성 패턴.
// 성공 — 값으로 암묵 변환absl::StatusOr<int> ok = 42;
// 실패 — Status로 암묵 변환absl::StatusOr<int> err = absl::InvalidArgumentError("...");
// 명시적absl::StatusOr<int> x(42);absl::StatusOr<int> y(absl::Status(absl::StatusCode::kNotFound, "..."));#API
absl::StatusOr<User> r = GetUser(42);
// 검사bool ok = r.ok();absl::Status s = r.status();absl::StatusCode code = r.status().code();
// 값 접근User u1 = *r; // dereference (unchecked)User u2 = r.value(); // ok이 아니면 LOG(FATAL) 또는 throwUser* p = &r.value();User u3 = std::move(r).value(); // 효율적 이동User u4 = *std::move(r);
// member accessr->member;(*r).member;operator*와 operator->는 ok을 가정한다. ok이 아니면 UB(release) 또는 LOG(FATAL)(debug). 안전한 접근은 value().
#내부 레이아웃
// 의사 코드template <typename T>class StatusOr {public: // ...private: union { Status status_; T value_; }; bool has_value_;};union으로 둘 중 하나만 들고 있다. has_value_ 비트로 어느 쪽인지 판별. 실제 구현은 더 정교하다 — Status의 ok 표현을 활용해 추가 비트를 안 쓰는 트릭이 있다.
크기:
sizeof(absl::StatusOr<int>) // sizeof(int) + sizeof(void*) + alignmentsizeof(absl::StatusOr<std::string>) // sizeof(std::string) + sizeof(void*) + alignmentT가 크면 stack 부담이 커질 수 있다. 일반적으로 작은 T(POD, smart pointer, string)에 적합.
#Move semantics
absl::StatusOr<std::vector<int>> GetData();
void Process() { auto r = GetData(); if (!r.ok()) return;
// 값 이동 — vector를 복사하지 않음 std::vector<int> v = std::move(r).value();
// 또는 std::vector<int> v2 = *std::move(r);}std::move(r).value() 패턴이 자주 보인다. rvalue로 변환해 내부 값을 이동.
#안 좋은 케이스
// 회피 — reference를 담은 StatusOrabsl::StatusOr<User&> GetUser(int id);// reference의 lifetime 문제
// Good — pointer 또는 valueabsl::StatusOr<User*> GetUser(int id);absl::StatusOr<User> GetUserCopy(int id);// 회피 — void에 StatusOrabsl::StatusOr<void> DoIt(); // 컴파일 에러// void는 값이 없음. Status 단독으로.
// Goodabsl::Status DoIt();StatusOr<void> 대신 Status. 너무 자주 보는 실수.
#Chaining
여러 단계를 거치는 코드.
absl::StatusOr<Config> LoadConfig(const std::string& path);absl::StatusOr<Server> CreateServer(const Config& c);absl::Status RunServer(Server* s);
absl::Status BootUp(const std::string& path) { auto config = LoadConfig(path); if (!config.ok()) return config.status();
auto server = CreateServer(*config); if (!server.ok()) return server.status();
return RunServer(&*server);}장황하다. 다음 편의 ASSIGN_OR_RETURN 매크로로 단축된다.
#Monadic 흐름 — 그림
이 패턴은 함수형 언어에서 map / flat_map / Result 모나드라고 부른다. happy path는 흐르고, error는 단락(short-circuit)된다.
ASSIGN_OR_RETURN이 하는 일은 이 그림의 error path를 언어 매크로로 흉내내는 것이다. C++23 std::expected의 .and_then() / .transform()은 같은 모델을 메서드로 노출한다.
#코드 리뷰 포인트
// 회피 — value()로 직접 dereference, ok 확인 없음User u = GetUser(42).value();// ok이 아니면 LOG(FATAL) 또는 throw. 비정상 종료.
// Goodauto r = GetUser(42);if (!r.ok()) { LOG(ERROR) << r.status(); return;}User u = *r;// 회피 — 큰 T를 값으로 반환 후 복사absl::StatusOr<std::vector<HugeStruct>> GetData();auto r = GetData();auto data = r.value(); // 복사
// Good — 이동auto data = std::move(r).value();// 회피 — StatusOr<T*>와 Owner의 혼동absl::StatusOr<User*> CreateUser();// 호출자가 delete 해야 하는지 모름.
// Good — unique_ptr 사용absl::StatusOr<std::unique_ptr<User>> CreateUser();리뷰에서:
- value() 전에 ok() 확인했는가.
- 큰 type에 대해 move 사용했는가.
- 소유권이 분명한가 — pointer 대신 unique_ptr.
#자주 보는 안티패턴
// 회피 — 항상 성공인데 StatusOrabsl::StatusOr<int> Identity(int x) { return x;}// 그냥 int 반환.
// Goodint Identity(int x) { return x; }// 회피 — StatusOr를 out param으로void GetUser(int id, absl::StatusOr<User>* out);// StatusOr는 return value용.
// Goodabsl::StatusOr<User> GetUser(int id);// 회피 — Status로 충분한데 StatusOr<bool>absl::StatusOr<bool> Exists(int id);// NotFound가 false, OK가 true로 표현 가능.
// Good — bool 자체가 의미 있는 정보면 OKabsl::StatusOr<bool> WasModified(int id); // 진짜 bool 결과// 회피 — 함수 깊은 곳에서 StatusOr를 풀고 다시 묶음absl::StatusOr<Config> Load(...) { auto r = ParseConfig(); if (!r.ok()) return r.status(); Config c = *r; // ... return c;}// 풀었다 묶는 비용. ASSIGN_OR_RETURN으로 단축 가능.
// Good — 다음 편 참조#std::expected와의 비교
C++23의 std::expected<T, E>가 비슷한 역할.
// C++23std::expected<int, std::string> ParseInt(std::string_view s);
// Abseilabsl::StatusOr<int> ParseInt(absl::string_view s);차이:
| 항목 | std::expected | absl::StatusOr |
|---|---|---|
| 에러 type | 임의 (E) | absl::Status 고정 |
| 표준 | C++23 | Abseil 의존 |
| canonical code | 사용자 정의 | 17개 표준 |
| payload | 사용자 정의 | URL-based payload |
| monadic | transform, and_then | (매크로로 대체) |
| 마이그레이션 | 간단 | StatusOr → expected는 매핑 필요 |
C++23이 사용 가능하고 canonical code가 필요 없으면 std::expected가 깔끔하다. canonical code, payload, gRPC 통합이 필요하면 StatusOr.
#정리
StatusOr<T>는 값과 에러를 한 type으로 묶는 패턴.- 내부는 union + flag. ok 경로에서 T를 직접 들고 있음.
value()는 ok 확인 후에.*operator는 unchecked.- 큰 type은 move로 추출.
StatusOr<void>는 없다.Status단독으로.- C++23의
std::expected가 일부 역할 대체. canonical code 필요하면StatusOr.
#다음 편
Part 3-03에서 ASSIGN_OR_RETURN, RETURN_IF_ERROR 매크로를 본다. chaining 코드의 장황함을 매크로로 어떻게 줄이고, 내부 expansion이 어떻게 작동하는지.
#관련 항목
Abseil Code Review · 16 of 79
- 1 Abseil Code Review — Google production-grade C++ 라이브러리 분석
- 2 Abseil 개요 — Google이 std를 보완한 이유
- 3 Abseil 설계 철학 — std 호환과 추가 기능의 균형
- 4 Abseil 빌드와 의존성 — Bazel vs CMake
- 5 Abseil LTS vs HEAD 릴리스 모델 분석
- 6 Abseil Versioning과 ABI 호환성 정책
- 7 Abseil 매크로 — ABSL_HAVE_*·ABSL_ATTRIBUTE_*
- 8 Abseil ABSL_PREDICT_TRUE/FALSE — branch hint
- 9 absl::LogSeverity — 로그 레벨 타입
- 10 Abseil type_traits — negation·conjunction·void_t
- 11 Abseil Conformance·Policy 분석
- 12 Abseil Memory utilities 분석
- 13 Abseil raw_logging — heap-free 로깅
- 14 Abseil thread_annotations — clang TSA 통합
- 15 absl::Status — exception-free error handling
- 16 absl::StatusOr<T> — 값 또는 에러
- 17 absl status_macros — ASSIGN_OR_RETURN·RETURN_IF_ERROR
- 18 absl::Status payload — 구조화된 에러 컨텍스트
- 19 absl::Status ↔ exception 변환 패턴
- 20 absl::string_view — non-owning 문자열 참조
- 21 absl::string_view 함정 — dangling·c_str·임시 객체
- 22 absl::StrCat — 가변 인자 문자열 연결과 AlphaNum
- 23 absl::StrSplit — Delimiter·Predicate·컨테이너 변환
- 24 absl::StrJoin — 컨테이너 결합과 Formatter
- 25 absl::StrFormat — type-safe printf·FormatSpec
- 26 Abseil ASCII 함수 — locale-free 분류·대소문자 변환
- 27 Abseil Escape — CEscape·HexEscape·Base64
- 28 absl::flat_hash_map — Swiss Table 기반 hash map
- 29 absl::flat_hash_set — set 버전 Swiss Table
- 30 absl::node_hash_map — stable pointer가 필요할 때
- 31 absl::btree_map — sorted·cache-friendly B-tree
- 32 absl::FixedArray — 런타임 크기 stack 배열
- 33 absl::InlinedVector — small buffer optimization
- 34 Abseil Swiss Table internals — control byte·SIMD probing
- 35 absl::Mutex — reader-writer·fairness·deadlock 검출
- 36 absl::Mutex Conditional Critical Section — Await로 cv 없애기
- 37 absl::Notification — once-only signal
- 38 absl::BlockingCounter·Barrier — 다중 thread 조율
- 39 absl::Mutex annotations — clang thread-safety로 race를 컴파일 타임에
- 40 absl::Time·Duration 분석 — 단단한 type
- 41 absl::Time Format·Parse
- 42 absl::CivilTime 분석
- 43 absl::time_zone 분석
- 44 absl::Time mocking — 테스트 친화 시간
- 45 absl::BitGen — 모던 난수 생성기
- 46 Abseil Random Distributions — Uniform·Exponential
- 47 Abseil Mocking Random — 테스트 결정성
- 48 Abseil Random Seeding·Entropy
- 49 absl::int128·uint128 분석
- 50 absl::bits — popcount·countl_zero
- 51 absl::optional vs std::optional
- 52 absl::variant 분석
- 53 absl::span 분석
- 54 absl::any 분석
- 55 absl::compare — three-way 비교
- 56 Abseil utility — apply·in_place
- 57 Abseil AbslHashValue 분석
- 58 Abseil HashState chaining
- 59 Abseil Custom hashable 구현
- 60 Abseil LOG·VLOG·CHECK 분석
- 61 Abseil LogSink 분석
- 62 Abseil LogEntry·structured logging
- 63 Abseil Stack trace·failure_signal_handler
- 64 ABSL_FLAG 정의 분석
- 65 Abseil ParseCommandLine 동작
- 66 Abseil Flag introspection·validation
- 67 Google 스타일의 Abseil 사용 패턴
- 68 Abseil 자주 보는 anti-pattern
- 69 std → absl 마이그레이션 전략
- 70 absl::Cleanup — 함수 종료 시 실행 보장
- 71 Abseil algorithm container 확장 — c_sort·c_find_if·c_count_if
- 72 absl::function_ref와 any_invocable — 함수 객체 전달의 두 축
- 73 absl::bind_front와 Overload — 함수 객체 보조 도구
- 74 absl::Cord — 분산 시스템용 대용량 문자열
- 75 absl::from_chars·SimpleAtoi — 빠른 숫자 변환
- 76 absl::Cord vs std::string — 선택 기준과 메모리 프로파일
- 77 absl::GetStackTrace와 Symbolize — crash 시 readable stack
- 78 absl::ComputeCrc32c — 하드웨어 가속 체크섬
- 79 absl::PeriodicSampler — 적응형 샘플링·jitter 회피
관련 글
absl status_macros — ASSIGN_OR_RETURN·RETURN_IF_ERROR
Part 3-03: RETURN_IF_ERROR와 ASSIGN_OR_RETURN — 에러 전파를 한 줄로. 매크로 expansion 분석과 안전한 사용법.
같은 시리즈에서 이어 읽기
absl::Status — exception-free error handling
Part 3-01: absl::Status — Google이 exception 없이 production C++ 에러를 다루는 방법. canonical error code, payload, 내부 표현.
같은 시리즈에서 이어 읽기
absl::PeriodicSampler — 적응형 샘플링·jitter 회피
absl::profiling_internal::PeriodicSampler — sampling rate를 동적으로 조정, geometric distribution으로 jitter 회피. 메모리 할당 추적·profiling 인프라의 기반.
같은 시리즈에서 이어 읽기
이 글을 참조하는 글 (6)
- absl::variant 분석 — Abseil Code Review
- absl::optional vs std::optional — Abseil Code Review
- absl::Status ↔ exception 변환 패턴 — Abseil Code Review
- absl::Status payload — 구조화된 에러 컨텍스트 — Abseil Code Review
- absl status_macros — ASSIGN_OR_RETURN·RETURN_IF_ERROR — Abseil Code Review
- absl::Status — exception-free error handling — Abseil Code Review