본문으로 건너뛰기
Abseil Code Review · 18/79

absl::Status ↔ exception 변환 패턴

· Hawk · 3분 읽기

한 줄 요약: Status 기반 코드와 exception 기반 외부 라이브러리, gRPC status, std::error_code 사이 변환은 라이브러리 경계에서만 일어나야 한다. 변환이 코드 곳곳에서 일어나면 디버깅도 성능도 잃는다.

#어떤 문제를 푸는가

Google 스타일은 exception 없음. 그러나 실제 코드베이스는 exception을 던지는 외부 라이브러리(std::filesystem, nlohmann::json, 일부 third-party)와 공존해야 한다. 동시에 gRPC, std::error_code, 시스템 errno와도 통신해야 한다.

변환 규칙이 필요하다.

  1. 언제 변환하는가 — 라이브러리 경계에서 한 번.
  2. 어느 방향으로 — 내부는 Status, 경계에서 변환.
  3. 정보 손실 최소화 — 양쪽의 추가 정보 보존.

#원칙: 경계에서만

// 회피 — 함수마다 변환
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-catch
absl::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 — ErrnoToStatus
return absl::ErrnoToStatus(err, "while reading file");

리뷰에서:

  1. try-catch가 함수마다 반복되지 않는가 — wrapper로 통합.
  2. 변환 시 정보 손실이 없는가 — canonical code, payload 보존.
  3. errno/error_code를 raw로 노출하지 않는가.

#자주 보는 안티패턴

// 회피 — exception을 catch해서 OK Status로
try {
DoSomething();
return absl::OkStatus();
} catch (...) {
return absl::OkStatus(); // 에러를 silent하게 삼킴
}
// Good
try {
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로
}
}

#변환 매트릭스

fromto함수
errnoabsl::Statusabsl::ErrnoToStatus(err, ctx)
std::error_codeabsl::Status사용자 정의 (위 예시)
grpc::Statusabsl::Status사용자 정의 (위 예시)
std::exceptionabsl::StatusToStatus(e) 사용자 정의
absl::Statusgrpc::Status사용자 정의
absl::Statusstd::exceptionStatusException(s)
absl::StatusabortCHECK_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. 1 Abseil Code Review — Google production-grade C++ 라이브러리 분석
  2. 2 Abseil 개요 — Google이 std를 보완한 이유
  3. 3 Abseil 설계 철학 — std 호환과 추가 기능의 균형
  4. 4 Abseil 빌드와 의존성 — Bazel vs CMake
  5. 5 Abseil LTS vs HEAD 릴리스 모델 분석
  6. 6 Abseil Versioning과 ABI 호환성 정책
  7. 7 Abseil 매크로 — ABSL_HAVE_*·ABSL_ATTRIBUTE_*
  8. 8 Abseil ABSL_PREDICT_TRUE/FALSE — branch hint
  9. 9 absl::LogSeverity — 로그 레벨 타입
  10. 10 Abseil type_traits — negation·conjunction·void_t
  11. 11 Abseil Conformance·Policy 분석
  12. 12 Abseil Memory utilities 분석
  13. 13 Abseil raw_logging — heap-free 로깅
  14. 14 Abseil thread_annotations — clang TSA 통합
  15. 15 absl::Status — exception-free error handling
  16. 16 absl::StatusOr<T> — 값 또는 에러
  17. 17 absl status_macros — ASSIGN_OR_RETURN·RETURN_IF_ERROR
  18. 18 absl::Status payload — 구조화된 에러 컨텍스트
  19. 19 absl::Status ↔ exception 변환 패턴
  20. 20 absl::string_view — non-owning 문자열 참조
  21. 21 absl::string_view 함정 — dangling·c_str·임시 객체
  22. 22 absl::StrCat — 가변 인자 문자열 연결과 AlphaNum
  23. 23 absl::StrSplit — Delimiter·Predicate·컨테이너 변환
  24. 24 absl::StrJoin — 컨테이너 결합과 Formatter
  25. 25 absl::StrFormat — type-safe printf·FormatSpec
  26. 26 Abseil ASCII 함수 — locale-free 분류·대소문자 변환
  27. 27 Abseil Escape — CEscape·HexEscape·Base64
  28. 28 absl::flat_hash_map — Swiss Table 기반 hash map
  29. 29 absl::flat_hash_set — set 버전 Swiss Table
  30. 30 absl::node_hash_map — stable pointer가 필요할 때
  31. 31 absl::btree_map — sorted·cache-friendly B-tree
  32. 32 absl::FixedArray — 런타임 크기 stack 배열
  33. 33 absl::InlinedVector — small buffer optimization
  34. 34 Abseil Swiss Table internals — control byte·SIMD probing
  35. 35 absl::Mutex — reader-writer·fairness·deadlock 검출
  36. 36 absl::Mutex Conditional Critical Section — Await로 cv 없애기
  37. 37 absl::Notification — once-only signal
  38. 38 absl::BlockingCounter·Barrier — 다중 thread 조율
  39. 39 absl::Mutex annotations — clang thread-safety로 race를 컴파일 타임에
  40. 40 absl::Time·Duration 분석 — 단단한 type
  41. 41 absl::Time Format·Parse
  42. 42 absl::CivilTime 분석
  43. 43 absl::time_zone 분석
  44. 44 absl::Time mocking — 테스트 친화 시간
  45. 45 absl::BitGen — 모던 난수 생성기
  46. 46 Abseil Random Distributions — Uniform·Exponential
  47. 47 Abseil Mocking Random — 테스트 결정성
  48. 48 Abseil Random Seeding·Entropy
  49. 49 absl::int128·uint128 분석
  50. 50 absl::bits — popcount·countl_zero
  51. 51 absl::optional vs std::optional
  52. 52 absl::variant 분석
  53. 53 absl::span 분석
  54. 54 absl::any 분석
  55. 55 absl::compare — three-way 비교
  56. 56 Abseil utility — apply·in_place
  57. 57 Abseil AbslHashValue 분석
  58. 58 Abseil HashState chaining
  59. 59 Abseil Custom hashable 구현
  60. 60 Abseil LOG·VLOG·CHECK 분석
  61. 61 Abseil LogSink 분석
  62. 62 Abseil LogEntry·structured logging
  63. 63 Abseil Stack trace·failure_signal_handler
  64. 64 ABSL_FLAG 정의 분석
  65. 65 Abseil ParseCommandLine 동작
  66. 66 Abseil Flag introspection·validation
  67. 67 Google 스타일의 Abseil 사용 패턴
  68. 68 Abseil 자주 보는 anti-pattern
  69. 69 std → absl 마이그레이션 전략
  70. 70 absl::Cleanup — 함수 종료 시 실행 보장
  71. 71 Abseil algorithm container 확장 — c_sort·c_find_if·c_count_if
  72. 72 absl::function_ref와 any_invocable — 함수 객체 전달의 두 축
  73. 73 absl::bind_front와 Overload — 함수 객체 보조 도구
  74. 74 absl::Cord — 분산 시스템용 대용량 문자열
  75. 75 absl::from_chars·SimpleAtoi — 빠른 숫자 변환
  76. 76 absl::Cord vs std::string — 선택 기준과 메모리 프로파일
  77. 77 absl::GetStackTrace와 Symbolize — crash 시 readable stack
  78. 78 absl::ComputeCrc32c — 하드웨어 가속 체크섬
  79. 79 absl::PeriodicSampler — 적응형 샘플링·jitter 회피