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

Abseil Memory utilities 분석

· Hawk · 3분 읽기

한 줄 요약: absl::memorystd::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

absl/memory/memory.h
template <typename T, typename... Args>
typename std::enable_if<!std::is_array<T>::value, std::unique_ptr<T>>::type
make_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>>::type
make_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();
// 안전한 wrapping
std::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_INLINEstd::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와의 비교

Abseilstd권장
absl::make_uniquestd::make_unique (C++14)std
absl::WrapUnique(없음)absl — factory에서 명시적 ownership
absl::make_optionalstd::make_optional (C++17)std
absl::nulloptstd::nullopt (C++17)std
absl::MakeCleanupstd::experimental::scope_exit (TS)absl — 표준화 안 됨

#코드 리뷰 포인트

// 회피 — make_unique 대신 new
auto p = std::unique_ptr<MyClass>(new MyClass(args));
// Good
auto p = std::make_unique<MyClass>(args);
// new가 꼭 필요할 때만 WrapUnique
auto p = absl::WrapUnique(new MyClass()); // private ctor 등 특수 상황
// 회피 — 수동 cleanup
FILE* f = fopen(path, "r");
if (!f) return;
// ... 여러 줄 ...
fclose(f); // 중간에 return 있으면 누수
// Good
FILE* 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);
// Good
auto opt = std::make_optional<MyClass>(args);

#자주 보는 안티패턴

// 회피 — unique_ptr 대신 raw new/delete
MyClass* p = new MyClass();
// ... 여러 줄 ...
delete p; // 예외 또는 early return 시 누수
// Good
auto 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.
// Good
auto 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. 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 회피