Abseil 자주 보는 anti-pattern
#anti-pattern을 모아 보는 이유
리뷰 코멘트의 80%는 같은 실수의 반복 이다. 한 번 정리해 두면 PR 작성 단계에서 스스로 잡아낼 수 있다. 부정 분류는 가볍게 “회피 vs Good” 쌍으로.
#1. string_view dangling
absl::string_view는 원본을 빌려보는 뷰 다. 원본보다 오래 살면 안 된다.
// 회피 — 임시 객체의 데이터 viewabsl::string_view Bad() { return std::string("hello"); // ❌ 임시 std::string 즉시 소멸}
absl::string_view sv = absl::StrCat("a", "b", "c"); // ❌ StrCat 결과 즉시 소멸
// Goodstd::string s = absl::StrCat("a", "b", "c");absl::string_view sv = s; // s 살아 있는 동안만 사용함수 매개변수로 받는 string_view는 호출자의 책임 으로 살아 있다고 가정한다. 함수 반환·멤버 저장은 위험.
// 회피 — 멤버 변수로 저장class Bad {public: explicit Bad(absl::string_view s) : s_(s) {} // ❌ s_가 dangle 가능private: absl::string_view s_;};
// Goodclass Good {public: explicit Good(absl::string_view s) : s_(s) {}private: std::string s_; // 복사 소유};#2. Mutex annotation 누락
Clang thread safety analysis는 주석된 mutex/필드만 검증한다. 누락하면 race가 잡히지 않는다.
// 회피class Bad {public: void Add(int x) { data_.push_back(x); } // ❌ lock 없이 수정private: absl::Mutex mu_; std::vector<int> data_; // ❌ GUARDED_BY 없음};
// Goodclass Good {public: void Add(int x) ABSL_LOCKS_EXCLUDED(mu_) { absl::MutexLock lock(&mu_); data_.push_back(x); }private: absl::Mutex mu_; std::vector<int> data_ ABSL_GUARDED_BY(mu_);};ABSL_GUARDED_BY, ABSL_LOCKS_EXCLUDED, ABSL_EXCLUSIVE_LOCKS_REQUIRED를 routine하게 붙인다.
#3. StatusOr / Status 무시
absl::Status / absl::StatusOr<T>는 nodiscard. 그래도 무시되는 경우가 흔하다.
// 회피absl::Status DoWork() { /* ... */ }
void Caller() { DoWork(); // ❌ status 무시 — 컴파일러 경고는 나지만 코드 지나감}
// 회피 — StatusOr value() 무방비absl::StatusOr<int> Compute();int v = Compute().value(); // ❌ 실패 시 throw
// Good — RETURN_IF_ERROR / ASSIGN_OR_RETURN 패턴absl::Status Caller() { RETURN_IF_ERROR(DoWork()); ASSIGN_OR_RETURN(int v, Compute()); use(v); return absl::OkStatus();}#4. Hash + Equality 불일치
AbslHashValue와 operator==가 다른 필드 집합을 보면 해시 컨테이너 invariant가 깨진다.
// 회피struct Bad { int id; std::string name; int version;
template <typename H> friend H AbslHashValue(H h, const Bad& b) { return H::combine(std::move(h), b.id); // id만 }
friend bool operator==(const Bad& a, const Bad& b) { return a.id == b.id && a.name == b.name; // id + name }};// ❌ a == b 인데 hash 다름 가능 (절대 안 됨) — 또는 그 반대규칙: a == b ⇒ hash(a) == hash(b). hash가 보는 필드 ⊆ equality가 보는 필드.
#5. flat_hash_map<K, V*> + 멤버에 dangling raw pointer
// 회피absl::flat_hash_map<int, std::vector<int>*> m;auto* v = m[42]; // 새 슬롯 — null pointerv->push_back(1); // ❌ UB
// Good — node_hash_map 또는 value 보유absl::flat_hash_map<int, std::vector<int>> m;m[42].push_back(1); // 자동 생성
// 또는 명시 insertauto [it, inserted] = m.try_emplace(42);it->second.push_back(1);#6. Duration int 변환
// 회피 — 단위 모호void Wait(int seconds);Wait(5); // ms? s?
// Goodvoid Wait(absl::Duration d);Wait(absl::Seconds(5));// 회피 — Duration → int 추출 후 다시 만들기int ms = static_cast<int>(absl::ToDoubleMilliseconds(d));absl::Duration restored = absl::Milliseconds(ms); // 정밀도 손실
// Good — Duration 그대로 전달absl::Duration restored = d;#7. ScopedMockLog 누락 → flaky test
// 회피 — 로그 출력 검증을 stderr capture로TEST(BadLog, Warning) { // stderr 캡처 + 문자열 매칭 ... ❌ 환경 의존, flaky}
// Good — ScopedMockLogTEST(GoodLog, Warning) { absl::ScopedMockLog log; EXPECT_CALL(log, Log(absl::LogSeverity::kWarning, _, HasSubstr("slow"))); log.StartCapturingLogs(); DoSlow();}#8. BitGen 매번 생성
// 회피int Pick() { absl::BitGen bg; // ❌ 매번 OS entropy 시드 — 비싸고 thread 충돌 return absl::Uniform(bg, 0, 100);}
// Good — thread_localthread_local absl::BitGen tls_bg;int Pick() { return absl::Uniform(tls_bg, 0, 100); }#9. SetFlag 후 캡처된 값 기대
// 회피const bool verbose = absl::GetFlag(FLAGS_verbose); // 캡처absl::SetFlag(&FLAGS_verbose, true);if (verbose) { ... } // ❌ 여전히 이전 값
// Good — 런타임 변경 감지 필요하면 매번 GetFlagif (absl::GetFlag(FLAGS_verbose)) { ... }또는 변경 불가 한 설정은 main에서 한 번 캡처 후 그것만 사용.
#10. absl::variant get without check
// 회피absl::variant<int, std::string> v = "hello";int x = absl::get<int>(v); // ❌ throw absl::bad_variant_access
// Goodif (auto* p = absl::get_if<int>(&v)) { use(*p);}#11. FormatTime/ParseTime의 time zone 가정
// 회피std::string s = absl::FormatTime("%H:%M:%S", t, absl::LocalTimeZone());// ❌ 호스트 의존 — 서버 reboot 시 시간대 바뀌면 결과 다름
// Good — 서비스 표준 TZstd::string s = absl::FormatTime(absl::RFC3339_full, t, absl::UTCTimeZone());#12. CHECK로 사용자 입력 검증
// 회피absl::Status Handle(const Request& req) { CHECK(!req.user_id().empty()); // ❌ 사용자가 빈 ID 보내면 서버 죽음}
// Goodabsl::Status Handle(const Request& req) { if (req.user_id().empty()) { return absl::InvalidArgumentError("user_id required"); }}CHECK는 invariant 위반 (자기 코드의 버그). 외부 입력 에러는 Status.
#13. StrCat에 std::string 임시 + string_view 결합
// 회피absl::string_view sv = absl::StrCat("prefix-", suffix); // ❌ 임시 string
// Goodstd::string s = absl::StrCat("prefix-", suffix);absl::string_view sv = s; // s 살아 있는 동안 OK#14. flat_hash_set의 iterator를 invalidate 후 사용
// 회피auto it = set.find(key);set.insert(new_key); // ❌ 재해싱 시 it invalidateif (it != set.end()) use(*it);
// Good — insert 후 다시 find 또는 insert 전에 사용auto it = set.find(key);if (it != set.end()) use(*it); // 먼저 사용set.insert(new_key);flat_hash_*는 어떤 mutation에서도 iterator·reference invalidate. 안정 reference가 필요하면 node_hash_map.
#15. Initialize 누락
// 회피int main(int argc, char** argv) { LOG(INFO) << "start"; // ❌ log 초기화 전 — flag 미적용}
// Goodint main(int argc, char** argv) { absl::ParseCommandLine(argc, argv); absl::InitializeSymbolizer(argv[0]); absl::InstallFailureSignalHandler({}); absl::InitializeLog(); LOG(INFO) << "start";}#빠른 자가 점검 표
| 영역 | 체크 |
|---|---|
| string_view | 멤버 저장 / 함수 반환 안 함 |
| Mutex | GUARDED_BY 모든 보호 필드에 |
| Status | RETURN_IF_ERROR / ASSIGN_OR_RETURN 사용 |
| Hash | hash vs equality 필드 일치 |
| flat_hash_* | iterator invalidation 의식 |
| Duration | int 대신 Duration 시그니처 |
| BitGen | thread_local 또는 멤버 |
| CHECK | invariant만 — 입력 검증은 Status |
| FormatTime | UTC + RFC3339 기본 |
| LOG | severity 적절, Initialize 호출 |
#정리
- 가장 빈번한 anti-pattern은 string_view dangling — 멤버 저장 금지.
- Mutex annotation 누락 은 race를 정적 분석에서 숨김 — 일관 적용.
- Status/StatusOr는
RETURN_IF_ERROR/ASSIGN_OR_RETURN패턴이 표준. - hash vs equality 필드 일치는 컨테이너 invariant.
- CHECK는 invariant 위반만, 사용자 입력은 Status로.
#다음 장 예고
Part 13-03: std → absl 마이그레이션 전략.
#관련 항목
Abseil Code Review · 68 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 회피
관련 글
Google 스타일의 Abseil 사용 패턴
Part 13-01: Google이 사내에서 Abseil을 어떻게 쓰는지 — code review에서 자주 보는 권장 패턴.
같은 시리즈에서 이어 읽기
absl::string_view 함정 — dangling·c_str·임시 객체
Part 4-02: string_view를 실전에서 잘못 쓰는 패턴 — dangling reference, c_str 변환 비용, 임시 std::string 바인딩.
같은 시리즈에서 이어 읽기
Abseil Code Review — Google production-grade C++ 라이브러리 분석
Google이 만든 Abseil C++ 라이브러리를 code review의 시선으로 읽는다. std를 보완하는 industrial-grade 도구의 설계 의도와 사용 패턴을 13 Parts 68편으로 살펴본다.
같은 시리즈에서 이어 읽기