absl::Status ↔ exception 변환 패턴
한 줄 요약: Status 기반 코드와 exception 기반 외부 라이브러리, gRPC status,
std::error_code사이 변환은 라이브러리 경계에서만 일어나야 한다. 변환이 코드 곳곳에서 일어나면 디버깅도 성능도 잃는다.
#어떤 문제를 푸는가
Google 스타일은 exception 없음. 그러나 실제 코드베이스는 exception을 던지는 외부 라이브러리(std::filesystem, nlohmann::json, 일부 third-party)와 공존해야 한다. 동시에 gRPC, std::error_code, 시스템 errno와도 통신해야 한다.
변환 규칙이 필요하다.
- 언제 변환하는가 — 라이브러리 경계에서 한 번.
- 어느 방향으로 — 내부는 Status, 경계에서 변환.
- 정보 손실 최소화 — 양쪽의 추가 정보 보존.
#원칙: 경계에서만
// 회피 — 함수마다 변환std::string ReadFile(const std::string& path) { try { return std::filesystem::read_file(path); } catch (const std::filesystem::filesystem_error& e) { throw std::runtime_error(e.what()); }}
absl::StatusOr<std::string> Process() { try { return ReadFile("..."); } catch (...) { return absl::InternalError("..."); }}
// Good — 한 경계에서만absl::StatusOr<std::string> ReadFileSafe(const std::string& path) { try { return std::filesystem::read_file(path); } catch (const std::filesystem::filesystem_error& e) { return absl::Status(ToStatusCode(e.code()), e.what()); }}
// 그 이후로는 Status 흐름absl::StatusOr<std::string> Process() { return ReadFileSafe("...");}try-catch를 한 함수에 캡슐화하고, 그 외에는 Status 흐름.
#exception → Status
표준 exception을 Status로 매핑.
absl::Status ToStatus(const std::exception& e) { // 가장 안전한 fallback return absl::InternalError(e.what());}
absl::Status ToStatus(const std::system_error& e) { return absl::Status(ToStatusCode(e.code()), e.what());}
absl::Status ToStatus(const std::filesystem::filesystem_error& e) { return absl::Status(ToStatusCode(e.code()), e.what());}
absl::Status ToStatus(const std::bad_alloc&) { return absl::ResourceExhaustedError("out of memory");}
absl::Status ToStatus(const std::out_of_range& e) { return absl::OutOfRangeError(e.what());}
absl::Status ToStatus(const std::invalid_argument& e) { return absl::InvalidArgumentError(e.what());}catch all 패턴.
template <typename F>auto CallSafely(F&& f) -> absl::StatusOr<decltype(f())> { try { return f(); } catch (const std::exception& e) { return ToStatus(e); } catch (...) { return absl::InternalError("unknown exception"); }}
// 사용absl::StatusOr<int> result = CallSafely([] { return external_library::do_something(); // throws});void 반환의 경우 SFINAE로 분기. 또는 두 overload.
#Status → exception
-fexceptions 빌드에서 Status를 exception으로 변환해야 할 때.
class StatusException : public std::exception {public: explicit StatusException(absl::Status s) : status_(std::move(s)) {} const char* what() const noexcept override { return status_.message().data(); } const absl::Status& status() const { return status_; }private: absl::Status status_;};
void ThrowIfNotOk(const absl::Status& s) { if (!s.ok()) throw StatusException(s);}-fno-exceptions 빌드에서는 throw 대신 LOG(FATAL).
void DieIfNotOk(const absl::Status& s) { if (!s.ok()) { LOG(FATAL) << "operation failed: " << s; }}CHECK 매크로의 변형도 있다.
#define CHECK_OK(expr) \ do { \ absl::Status _s = (expr); \ if (ABSL_PREDICT_FALSE(!_s.ok())) { \ LOG(FATAL) << "CHECK_OK failed: " << #expr << ": " << _s; \ } \ } while (0)
CHECK_OK(DoCriticalOp());#std::error_code ↔ Status
POSIX errno 기반.
absl::Status FromErrno(int err) { return absl::ErrnoToStatus(err, "");}
absl::Status FromErrnoMessage(int err, absl::string_view ctx) { return absl::ErrnoToStatus(err, ctx);}absl::ErrnoToStatus는 Abseil이 제공. errno를 canonical code로 매핑.
// 의사 코드absl::Status ErrnoToStatus(int err, absl::string_view msg) { auto code = [err]() { switch (err) { case EACCES: case EPERM: return absl::StatusCode::kPermissionDenied; case EEXIST: return absl::StatusCode::kAlreadyExists; case ENOENT: return absl::StatusCode::kNotFound; case EINVAL: return absl::StatusCode::kInvalidArgument; case ENOMEM: return absl::StatusCode::kResourceExhausted; case ETIMEDOUT: return absl::StatusCode::kDeadlineExceeded; case ENOSYS: return absl::StatusCode::kUnimplemented; default: return absl::StatusCode::kUnknown; } }(); return absl::Status(code, absl::StrCat(msg, msg.empty() ? "" : ": ", strerror(err)));}std::error_code도 비슷하게.
absl::Status FromErrorCode(const std::error_code& ec) { if (!ec) return absl::OkStatus(); if (ec.category() == std::generic_category()) { return ErrnoToStatus(ec.value(), ""); } return absl::UnknownError(ec.message());}#gRPC Status ↔ Abseil Status
gRPC의 status code는 Abseil의 canonical code와 1
일치.#include <grpcpp/grpcpp.h>
absl::Status FromGrpcStatus(const grpc::Status& gs) { return absl::Status( static_cast<absl::StatusCode>(gs.error_code()), gs.error_message());}
grpc::Status ToGrpcStatus(const absl::Status& s) { return grpc::Status( static_cast<grpc::StatusCode>(s.code()), std::string(s.message()));}payload까지 변환하는 경우.
absl::Status FromGrpcStatusWithDetails(const grpc::Status& gs) { auto s = FromGrpcStatus(gs);
google::rpc::Status proto; if (proto.ParseFromString(gs.error_details())) { for (const auto& detail : proto.details()) { s.SetPayload(detail.type_url(), absl::Cord(detail.value())); } } return s;}#코드 리뷰 포인트
// 회피 — 매 함수마다 try-catchabsl::StatusOr<X> MyFunc() { try { return ExternalLib::DoIt(); } catch (...) { return absl::InternalError("..."); }}// 호출 chain 전체에서 반복.
// Good — wrapper에서 한 번absl::StatusOr<X> ExternalLibSafe::DoIt() { try { ... } catch (...) { ... }}absl::StatusOr<X> MyFunc() { return ExternalLibSafe::DoIt(); // 이미 Status 흐름}// 회피 — Status를 silent하게 throw로 변환void Process() { auto s = SomeOp(); if (!s.ok()) throw std::runtime_error(s.message()); // 정보 손실}
// Good — StatusException 사용void Process() { auto s = SomeOp(); if (!s.ok()) throw StatusException(s); // Status 전체 보존}// 회피 — errno를 직접 노출int err = ...;return absl::InternalError(absl::StrCat("errno=", err));
// Good — ErrnoToStatusreturn absl::ErrnoToStatus(err, "while reading file");리뷰에서:
- try-catch가 함수마다 반복되지 않는가 — wrapper로 통합.
- 변환 시 정보 손실이 없는가 — canonical code, payload 보존.
- errno/error_code를 raw로 노출하지 않는가.
#자주 보는 안티패턴
// 회피 — exception을 catch해서 OK Status로try { DoSomething(); return absl::OkStatus();} catch (...) { return absl::OkStatus(); // 에러를 silent하게 삼킴}
// Goodtry { DoSomething(); return absl::OkStatus();} catch (const std::exception& e) { return ToStatus(e);}// 회피 — Status code 무시absl::Status s = SomeOp();if (!s.ok()) { return absl::InternalError(std::string(s.message())); // canonical code 잃음}
// Good — 원본 status 그대로 전파absl::Status s = SomeOp();if (!s.ok()) return s;// 회피 — exception을 abort로void Foo() { try { ExternalLib::DoIt(); } catch (...) { std::abort(); // 너무 폭력적 }}
// Good — 분류 가능하면 status, 진짜 invariant 위반이면 LOG(FATAL)void Foo() { try { ExternalLib::DoIt(); } catch (const std::bad_alloc&) { LOG(FATAL) << "OOM"; // 회복 불가 } catch (const std::exception& e) { LOG(ERROR) << "operation failed: " << e.what(); // 호출자에게 전파 가능하면 status로 }}#변환 매트릭스
| from | to | 함수 |
|---|---|---|
errno | absl::Status | absl::ErrnoToStatus(err, ctx) |
std::error_code | absl::Status | 사용자 정의 (위 예시) |
grpc::Status | absl::Status | 사용자 정의 (위 예시) |
std::exception | absl::Status | ToStatus(e) 사용자 정의 |
absl::Status | grpc::Status | 사용자 정의 |
absl::Status | std::exception | StatusException(s) |
absl::Status | abort | CHECK_OK(s) |
#정리
- 변환은 라이브러리 경계에서만. 코드 내부는 한 종류 (Status 또는 exception)로 통일.
absl::ErrnoToStatus는 errno → Status 표준.- gRPC status는 1 매핑. payload는 별도 처리.
- exception → Status는 fallback으로
InternalError. 가능하면 분류. - Status → exception은
StatusException같은 wrapper로 정보 보존.
#다음 편
Part 3을 마치고 Part 4로 넘어간다. Part 4-01에서 absl::string_view를 본다. C++17의 std::string_view와 어떻게 다른지, lifetime 함정은 어떻게 피하는지 다룬다.
#관련 항목
Abseil Code Review · 19 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 payload — 구조화된 에러 컨텍스트
Part 3-04: Status payload — URL-based key로 구조화된 컨텍스트를 첨부, gRPC error_details와 연동.
같은 시리즈에서 이어 읽기
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, 내부 표현.
같은 시리즈에서 이어 읽기