Abseil Custom hashable 구현
#표준 hashable과 보너스
Abseil의 absl::Hash는 다음 타입을 기본으로 hashable로 인식한다.
- 모든 정수·실수·포인터
bool,char,wchar_t등 문자형std::string,absl::string_viewstd::pair,std::tuple(요소가 hashable)std::vector,std::array,std::set,std::map등 표준 컨테이너absl::flat_hash_set,absl::node_hash_set(combine_unordered)absl::optional,absl::variantabsl::Time,absl::Duration
이 위에 사용자 타입만 AbslHashValue를 추가하면 된다.
#패턴 1 — 단순 value class
struct UserId { int64_t value;
template <typename H> friend H AbslHashValue(H h, const UserId& id) { return H::combine(std::move(h), id.value); }
friend bool operator==(UserId a, UserId b) { return a.value == b.value; }};
absl::flat_hash_map<UserId, User> users;users[UserId{42}] = ...;내부 타입(int64_t)이 hashable이므로 한 줄.
#패턴 2 — 다중 필드
struct CompositeKey { std::string region; int64_t user_id; absl::CivilDay date;
template <typename H> friend H AbslHashValue(H h, const CompositeKey& k) { return H::combine(std::move(h), k.region, k.user_id, k.date); }
friend bool operator==(const CompositeKey& a, const CompositeKey& b) { return a.region == b.region && a.user_id == b.user_id && a.date == b.date; }};CivilDay도 Abseil이 기본 hashable로 제공.
#패턴 3 — enum class
enum class는 기본 hashable이지만 intentional하게 friend 함수를 두면 가독성이 좋다.
enum class Region : int { kUS, kEU, kAP };
template <typename H>H AbslHashValue(H h, Region r) { return H::combine(std::move(h), static_cast<int>(r));}absl::Hash는 enum class를 underlying type으로 자동 처리하므로 작성 불필요 하지만, namespace 안의 의도 표현용으로 쓸 수 있다.
#패턴 4 — bytes / raw data
이미지 hash, 바이너리 키 등 raw 바이트:
struct Bytes { std::vector<uint8_t> data;
template <typename H> friend H AbslHashValue(H h, const Bytes& b) { return H::combine_contiguous(std::move(h), b.data.data(), b.data.size()); }};combine_contiguous로 trivial 타입 N개를 바이트 묶음으로 처리. 일반 combine(h, vec)보다 빠르다.
#패턴 5 — set-like (순서 무관)
class TagSet {public: absl::flat_hash_set<std::string> tags;
template <typename H> friend H AbslHashValue(H h, const TagSet& t) { return H::combine_unordered(std::move(h), t.tags.begin(), t.tags.end()); }
friend bool operator==(const TagSet& a, const TagSet& b) { return a.tags == b.tags; }};combine_unordered가 순서를 흡수 — {"a", "b"} 와 {"b", "a"} 가 같은 해시.
#패턴 6 — 비공개 필드 (friend)
class Money {public: Money(int64_t cents, std::string currency) : cents_(cents), currency_(std::move(currency)) {}
template <typename H> friend H AbslHashValue(H h, const Money& m) { return H::combine(std::move(h), m.cents_, m.currency_); }
friend bool operator==(const Money& a, const Money& b) { return a.cents_ == b.cents_ && a.currency_ == b.currency_; }
private: int64_t cents_; std::string currency_;};friend라 private 멤버 접근 가능. 외부 namespace 침범 없음.
#패턴 7 — std::hash 호환 추가
std 컨테이너도 함께 받으려면:
struct MyKey { int x, y;
template <typename H> friend H AbslHashValue(H h, const MyKey& k) { return H::combine(std::move(h), k.x, k.y); }
friend bool operator==(MyKey a, MyKey b) { return a.x == b.x && a.y == b.y; }};
// std::unordered_map<MyKey, V> 사용을 위해namespace std {template <>struct hash<MyKey> { size_t operator()(const MyKey& k) const noexcept { return absl::Hash<MyKey>{}(k); }};}absl::Hash로 위임하므로 알고리즘은 한 곳에서만 정의된다.
#회피 패턴
// 회피 — 일부 필드만 hash + 다른 필드까지 ==struct Bad { int id; std::string name; int version; // hash에는 안 들어가지만 ==에는 들어감
template <typename H> friend H AbslHashValue(H h, const Bad& b) { return H::combine(std::move(h), b.id, b.name); // version 빠짐 }
friend bool operator==(const Bad& a, const Bad& b) { return a.id == b.id && a.name == b.name && a.version == b.version; // ❌ a == b 이지만 hash 다를 수 있음 — 해시 컨테이너 invariant 위반 }};규칙: a == b ⇒ hash(a) == hash(b). 두 함수의 필드 집합은 동일 해야 한다. 차이가 나면 해시 컨테이너에서 조회 실패가 일어난다.
// 회피 — 부동소수 hashstruct Bad { double x; template <typename H> friend H AbslHashValue(H h, const Bad& b) { return H::combine(std::move(h), b.x); // ❌ NaN != NaN인데 같은 비트면 hash 같음 — operator==의 의미와 충돌 }};부동소수를 해시 키로 쓰는 것 자체가 보통 설계 실수다. 필요하면 NaN 정규화 + 부동소수 비교 정책을 명시.
#큰 객체에 대한 해시 캐싱
해시 계산이 무거우면 캐싱 을 고려한다.
class HugeKey {public: HugeKey(std::string s) : s_(std::move(s)), h_(absl::Hash<std::string>{}(s_)) {}
template <typename H> friend H AbslHashValue(H h, const HugeKey& k) { return H::combine(std::move(h), k.h_); // 미리 계산 }
friend bool operator==(const HugeKey& a, const HugeKey& b) { return a.h_ == b.h_ && a.s_ == b.s_; // hash 같지 않으면 빠른 fail }
private: std::string s_; size_t h_; // cached};객체가 immutable일 때만 안전. 멤버 변경 시 캐시 동기화가 어렵다.
#정리
- 사용자 타입은
AbslHashValuefriend 함수 한 줄로 hashable. operator==와 같은 필드 집합 을 covering 해야 한다.combine/combine_contiguous/combine_unordered셋 중 의도에 맞춰 선택.- enum class는 자동 처리 — 명시 정의 불필요.
- std 컨테이너 호환은
std::hash특수화에서absl::Hash로 위임.
#다음 장 예고
Part 11-01: LOG, VLOG, CHECK — Abseil logging.
#관련 항목
Abseil Code Review · 59 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 회피
관련 글
Abseil HashState chaining
H::combine의 진짜 동작 — HashState로 필드를 chain해 한 번에 좋은 분포를 얻는 방법.
같은 시리즈에서 이어 읽기
Abseil AbslHashValue 분석
AbslHashValue — std::hash 특수화 대신 ADL 기반 friend 함수로 hash를 정의하는 Abseil의 방식.
같은 시리즈에서 이어 읽기
absl::PeriodicSampler — 적응형 샘플링·jitter 회피
absl::profiling_internal::PeriodicSampler — sampling rate를 동적으로 조정, geometric distribution으로 jitter 회피. 메모리 할당 추적·profiling 인프라의 기반.
같은 시리즈에서 이어 읽기