Abseil Memory utilities 분석
한 줄 요약:
absl::memory는std::make_unique,allocator_traits같은 표준 도구의 polyfill과 보완을 제공한다. 현대 C++(14+)에서는 대부분 std로 옮겨갔지만, allocator-aware 구현에 필요한 일부 helper는 여전히 가치가 있다.
#어떤 문제를 푸는가
C++11은 std::unique_ptr을 도입했지만 std::make_unique를 빠뜨렸다. C++14에서 추가됐다. Abseil은 C++11 시절부터 absl::make_unique를 제공해 사용자가 직접 new를 쓸 필요가 없게 했다.
이제 C++14 이상이 표준이므로 absl::make_unique는 사실상 std::make_unique의 alias다. 그러나 allocator 관련 helper, uninitialized memory 다루기, raw pointer wrapping 같은 부분은 std에 부족하다.
#absl::make_unique
template <typename T, typename... Args>typename std::enable_if<!std::is_array<T>::value, std::unique_ptr<T>>::typemake_unique(Args&&... args) { return std::unique_ptr<T>(new T(std::forward<Args>(args)...));}C++14의 std::make_unique와 동등. 새 코드는 std를 쓰는 것이 권장된다.
// 권장auto p = std::make_unique<MyClass>(arg1, arg2);
// C++11 호환이 필요한 코드에서만auto p = absl::make_unique<MyClass>(arg1, arg2);배열 버전도 제공.
template <typename T>typename std::enable_if<std::is_array<T>::value && std::extent<T>::value == 0, std::unique_ptr<T>>::typemake_unique(size_t n) { return std::unique_ptr<T>(new typename std::remove_extent<T>::type[n]());}
// 사용auto arr = absl::make_unique<int[]>(100);#absl::WrapUnique
raw pointer를 unique_ptr로 감싸는 명시적 함수.
template <typename T>std::unique_ptr<T> WrapUnique(T* ptr) { static_assert(!std::is_array<T>::value, "array types are unsupported"); static_assert(std::is_object<T>::value, "non-object types are unsupported"); return std::unique_ptr<T>(ptr);}왜 필요한가. std::unique_ptr<T>(p) 생성자는 explicit이지만, factory 함수에서 일관된 표현이 도움 된다.
// C 라이브러리 함수가 raw pointer 반환extern Widget* CreateWidget();
// 안전한 wrappingstd::unique_ptr<Widget> w = absl::WrapUnique(CreateWidget());
// new와 함께 쓰는 경우 — make_unique를 못 쓸 때class Foo {private: Foo(); // private constructor friend std::unique_ptr<Foo> CreateFoo();};
std::unique_ptr<Foo> CreateFoo() { // make_unique는 private constructor 호출 못 함 return absl::WrapUnique(new Foo());}WrapUnique의 이점은 ownership transfer를 코드에서 명확히 드러내는 것. new 다음에 바로 WrapUnique를 호출하는 패턴은 “이 pointer가 unique_ptr에 들어간다”는 의도를 명시한다.
#absl::pointer_traits 등
과거 absl::pointer_traits가 있었으나 지금은 ABSL_DEPRECATE_AND_INLINE로 std::pointer_traits에 위임된다. 보완 도구는 거의 없고 대부분 std를 그대로 쓴다.
#allocator_traits 보완
Abseil의 컨테이너(flat_hash_map 등) 구현은 std::allocator_traits를 적극 활용한다. 일부 helper는 internal namespace에서만 노출되어 있다.
// 의사 코드 — Abseil 내부namespace container_internal {
template <typename Alloc, typename T, typename... Args>void Construct(Alloc* alloc, T* ptr, Args&&... args) { std::allocator_traits<Alloc>::construct(*alloc, ptr, std::forward<Args>(args)...);}
template <typename Alloc, typename T>void Destroy(Alloc* alloc, T* ptr) { std::allocator_traits<Alloc>::destroy(*alloc, ptr);}
} // namespace container_internal이 helper들은 public API가 아니다. 사용자 코드가 직접 부르지 말 것. 컨테이너를 만들 때는 표준 std::allocator_traits를 직접 사용한다.
#absl::nullopt 등의 관련 utility
엄밀히는 type utility지만 memory와 함께 자주 쓰인다.
absl::optional<MyClass> opt;opt = absl::nullopt;
absl::optional<MyClass> opt2 = absl::make_optional<MyClass>(arg1, arg2);C++17 이상에서는 std로 옮긴다.
std::optional<MyClass> opt = std::nullopt;auto opt2 = std::make_optional<MyClass>(arg1, arg2);#absl::Cleanup — RAII helper
C++20의 std::scope_guard와 비슷한 역할. (Abseil이 먼저 만들었고 표준이 따라잡은 케이스)
#include "absl/cleanup/cleanup.h"
void ProcessFile(const std::string& path) { FILE* f = fopen(path.c_str(), "r"); if (!f) return; auto close = absl::MakeCleanup([f]() { fclose(f); });
// 사용 Read(f); // close가 scope 끝에서 자동 호출}명시적 destructor 없이 RAII를 추가할 수 있다. 짧은 함수 안에서 한두 개의 자원을 깔끔하게 처리하기 위한 도구. unique_ptr를 쓸 정도로 정형화되지 않은 자원에 적합.
#취소 가능한 cleanup
auto cleanup = absl::MakeCleanup([&] { Rollback(); });
if (CommitWasSuccessful()) { std::move(cleanup).Cancel(); // cleanup 비활성화}// 성공이면 Rollback() 안 호출이 패턴은 “성공할 때만 cleanup을 건너뛰기” 즉 transactional 코드에서 유용하다.
#std::pmr과의 관계
C++17이 도입한 polymorphic allocator는 Abseil이 별도로 다루지 않는다. std::pmr::*를 그대로 사용한다. Abseil의 컨테이너는 std::pmr::polymorphic_allocator도 사용할 수 있다.
std::pmr::monotonic_buffer_resource pool;absl::flat_hash_map<int, std::string, absl::Hash<int>, std::equal_to<int>, std::pmr::polymorphic_allocator<std::pair<const int, std::string>>> map{&pool};#std와의 비교
| Abseil | std | 권장 |
|---|---|---|
absl::make_unique | std::make_unique (C++14) | std |
absl::WrapUnique | (없음) | absl — factory에서 명시적 ownership |
absl::make_optional | std::make_optional (C++17) | std |
absl::nullopt | std::nullopt (C++17) | std |
absl::MakeCleanup | std::experimental::scope_exit (TS) | absl — 표준화 안 됨 |
#코드 리뷰 포인트
// 회피 — make_unique 대신 newauto p = std::unique_ptr<MyClass>(new MyClass(args));
// Goodauto p = std::make_unique<MyClass>(args);
// new가 꼭 필요할 때만 WrapUniqueauto p = absl::WrapUnique(new MyClass()); // private ctor 등 특수 상황// 회피 — 수동 cleanupFILE* f = fopen(path, "r");if (!f) return;// ... 여러 줄 ...fclose(f); // 중간에 return 있으면 누수
// GoodFILE* f = fopen(path, "r");if (!f) return;auto close = absl::MakeCleanup([f] { fclose(f); });// ... return이 어디서든 close 자동 호출// 회피 — absl::make_optional을 C++17 코드에서 사용auto opt = absl::make_optional<MyClass>(args);
// Goodauto opt = std::make_optional<MyClass>(args);#자주 보는 안티패턴
// 회피 — unique_ptr 대신 raw new/deleteMyClass* p = new MyClass();// ... 여러 줄 ...delete p; // 예외 또는 early return 시 누수
// Goodauto p = std::make_unique<MyClass>();// 회피 — Cleanup을 변수 이름 없이 사용absl::MakeCleanup([f] { fclose(f); });// 이 표현식의 결과는 임시 객체. 다음 줄에서 destructor 호출됨.// 즉시 cleanup 실행.
// Good — 변수에 바인딩auto close = absl::MakeCleanup([f] { fclose(f); });// 회피 — WrapUnique를 make_unique 대신auto p = absl::WrapUnique(new MyClass(args));// 작동하지만 make_unique가 더 안전. exception-safe.
// Goodauto p = std::make_unique<MyClass>(args);#정리
absl::make_unique는 C++14의std::make_unique로 사실상 대체됨.absl::WrapUnique는 raw pointer를 unique_ptr로 명시적으로 wrapping할 때 유용.absl::MakeCleanup은 RAII가 정형화되지 않은 자원에 대한 가벼운 helper. Cancel 가능.- allocator_traits 관련 internal helper는 사용자가 직접 부르지 말 것.
#다음 편
Part 2-07에서 raw_logging을 본다. 일반 로깅 시스템이 초기화되기 전이나 heap을 쓸 수 없는 환경에서 어떻게 안전하게 로그를 남기는지.
#관련 항목
Abseil Code Review · 12 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::Cord vs std::string — 선택 기준과 메모리 프로파일
absl::Cord와 std::string 중 무엇을 쓸지 판단하는 기준 — 크기·mutation 패턴·공유 빈도·메모리 프로파일 비교.
같은 시리즈에서 이어 읽기
Abseil raw_logging — heap-free 로깅
Part 2-07: raw_logging — heap, exception, mutex 없이 동작하는 로깅. signal handler, ASan early init, OOM 경로.
같은 시리즈에서 이어 읽기
Abseil Conformance·Policy 분석
Part 2-05: Abseil의 platform conformance 정책 — 지원 컴파일러, 표준 버전, deprecated_if_unavailable.
같은 시리즈에서 이어 읽기