absl::function_ref와 any_invocable — 함수 객체 전달의 두 축
#한 줄 요약
absl::FunctionRef<R(Args...)>는 함수 객체를 빌려 보는 non-owning 참조다. absl::AnyInvocable<R(Args...)>는 movable-only callable owner로 std::function의 한계(copy 강제, 큰 SBO)를 해소한 대체품이다. 콜백을 호출만 할 거면 FunctionRef, 저장하면 AnyInvocable이 기본이다.
#동기
callable을 매개변수로 받는 인터페이스는 흔하다. 표준 도구 두 개가 있지만 각각 문제가 있다.
template <class F>— 헤더에 구현을 노출해야 하고 컴파일 시간이 늘어난다.std::function<R(Args...)>— copy 생성자를 요구하고 type erasure를 위한 heap alloc이 잦다.
이를 정리하면:
// 회피 1 — 헤더 의존 강제template <typename Pred>void ForEach(Pred p);
// 회피 2 — copy 가능 강제 + allocvoid ForEach(std::function<void(int)> p);std::function은 std::move_only_function이 C++23에 들어오기 전까지 움직임만 되는 lambda (예: unique_ptr 캡처)를 받을 수 없다. 동기 콜백에 대해 callable을 복사할 이유가 없는데도 복사 비용을 지불한다.
#FunctionRef — 호출만 할 거면 충분하다
FunctionRef는 두 워드(callable* + invoker*)만 갖는 view다.
#include "absl/functional/function_ref.h"
void ForEach(absl::FunctionRef<void(int)> visit);
ForEach([](int x) { LOG(INFO) << x; });ForEach(std::function<void(int)>(SomeFreeFn));ForEach(SomeFreeFn);호출자 lambda는 호출 동안만 살아 있으면 된다. heap alloc 없다.
// absl/functional/function_ref.h (요약)template <typename R, typename... Args>class FunctionRef<R(Args...)> { using Invoker = R (*)(intptr_t, Args...);
intptr_t ptr_; Invoker invoker_;
public: template <typename F> FunctionRef(F&& f) noexcept { ptr_ = reinterpret_cast<intptr_t>(std::addressof(f)); invoker_ = [](intptr_t p, Args... args) -> R { return (*reinterpret_cast<F*>(p))(std::forward<Args>(args)...); }; }
R operator()(Args... args) const { return invoker_(ptr_, std::forward<Args>(args)...); }};caller에서 만든 lambda의 주소를 들고 있다가 호출 시 invoker를 통해 trampoline한다. 16바이트, alloc 0, 호출 1 indirect.
제약: lifetime이 호출 동안만 유효해야 하므로 저장 금지. 멤버 변수에 넣으면 거의 확실히 dangling.
// 회피class Worker { absl::FunctionRef<void()> cb_; // 곧 dangling};
// Good — 보관할 거면 AnyInvocable 또는 std::functionclass Worker { absl::AnyInvocable<void()> cb_;};#AnyInvocable — std::function의 대체
AnyInvocable은 callable을 소유하지만 std::function이 강제하는 복사 가능 요구를 없앤다.
#include "absl/functional/any_invocable.h"
absl::AnyInvocable<void()> task = [up = std::make_unique<int>(42)] { LOG(INFO) << *up;};
scheduler.Post(std::move(task));std::function이라면 위 lambda를 받지 못한다(unique_ptr 캡처 → non-copyable). AnyInvocable은 movable만 요구한다.
#const, noexcept, & qualifier 지원
AnyInvocable은 호출 qualifier를 시그니처에 인코딩한다.
absl::AnyInvocable<int() const> f1; // const-callable만absl::AnyInvocable<int() &&> f2; // 한 번만 호출 (소비)absl::AnyInvocable<int() noexcept> f3; // noexcept callable만absl::AnyInvocable<int(int) const noexcept> f4;특히 R() &&는 한 번 실행하고 소비하는 일회성 task를 안전하게 표현한다. std::function은 이를 표현할 수 없다.
#SBO
AnyInvocable은 작은 callable을 내부 버퍼에 in-place로 저장한다 (보통 3 워드, 24 바이트). 그 이상은 heap alloc.
// 무료 — SBO 적용absl::AnyInvocable<void()> f = [] { ... };
// alloc — 캡처가 큼absl::AnyInvocable<void()> g = [v = std::vector<int>(100)] { use(v); };std::function도 SBO를 가지지만 표준이 크기를 규정하지 않아 구현 의존이다. AnyInvocable은 명시된 크기를 갖는다.
#비교 표
| 항목 | function_ref | any_invocable | std::function | std::move_only_function (C++23) |
|---|---|---|---|---|
| 소유 | × | O | O | O |
| copy | — | × | O | × |
| move | — | O | O | O |
| 비용 | 16B view | 24B SBO + alloc | 32B SBO + alloc | 32B SBO + alloc |
| const/noexcept qualifier | × | O | × | O |
| 표준 | — | — | C++11 | C++23 |
#코드 리뷰 포인트
1. “호출만” 매개변수면 FunctionRef
// 회피 — alloc + copy 강제void Each(std::function<void(int)> visit);
// Goodvoid Each(absl::FunctionRef<void(int)> visit);Each가 callback을 저장하지 않고 호출만 한다면 alloc할 이유가 없다.
2. 저장은 AnyInvocable, std::function 권장 회피
// 회피 — copy 강제 + 큰 SBOclass TaskQueue { std::vector<std::function<void()>> queue_;};
// Good — move-only OK, 작은 SBOclass TaskQueue { std::vector<absl::AnyInvocable<void() &&>> queue_;};&& qualifier로 한 번만 호출 의도를 시그니처에 표시한다.
3. template parameter 회피하고 싶을 때
// 회피 — 모든 호출자가 헤더로template <typename Pred>inline void ForEachLine(Pred p);
// Good — .cc로 이동void ForEachLine(absl::FunctionRef<void(absl::string_view)> p);template 폭증을 막는 가장 가벼운 도구.
4. lifetime은 호출자가 책임
absl::FunctionRef<void()> Cache() { return [] { LOG(INFO) << "cached"; }; // dangling — lambda는 임시}FunctionRef는 절대 반환 타입으로 쓰지 않는다.
#std::function과 무엇이 진짜 다른가
std::function은 1990년대 Boost에서 출발한 디자인이다. 그 시절의 결정이 굳어 있다.
- copy 요구는 message passing 이전의 가정.
- type erasure에 대한 SBO 크기 표준이 없다.
const,noexceptqualifier를 시그니처에 인코딩하지 못한다.
absl::AnyInvocable은 이 셋을 모두 해결한 Google이 사내에서 권장하는 표준 교체다. std::move_only_function(C++23)이 일부 격차를 메우지만 noexcept qualifier와 SBO 크기 명시는 여전히 AnyInvocable이 앞선다.
#자주 보는 안티패턴
FunctionRef 멤버 저장
class Worker { absl::FunctionRef<void()> cb_; void Run() { cb_(); } // ❌ 거의 확실히 dangling};생성자에서 받은 FunctionRef는 곧 호출 컨텍스트가 끝나면 무효. 저장하려면 AnyInvocable.
std::function을 FunctionRef로 받아 복사 회피 의도
// 호출자는 std::function을 들고 있고std::function<void()> f = ...;SomeAPI(f); // SomeAPI는 absl::FunctionRef<void()>
// 의도와 다르게 SomeAPI 내부에서 f를 저장하려 하면 망가짐API 인터페이스가 FunctionRef라면 저장하지 않겠다는 약속이다. 약속을 깨면 dangling.
AnyInvocable에 lambda 매번 alloc
for (;;) { scheduler.Post(absl::AnyInvocable<void()>([v = std::vector<int>(1000)] {...}));}대용량 캡처는 매 호출 alloc. 가능하면 pool 또는 shared state로 이동.
#정리
FunctionRef는 호출만 할 매개변수. 16바이트 view, alloc 0.AnyInvocable은 저장하는 callable owner. movable,const/noexcept/&&qualifier 지원.std::function은 copy 요구·SBO 미규정의 옛 디자인.AnyInvocable로 갈아탄다.FunctionRef는 절대 멤버 변수·반환 타입 금지.
#다음 편
Part 14-04 — bind_front와 Overload에서 보조 함수 객체 도구를 본다.
#관련 항목
- Part 14-04 — bind_front / Overload
- Part 14-01 — Cleanup
- Part 9-04 — absl::variant
- Folly Part 4-03 — folly::Function — Meta의 move-only callable
- EMC++ Item 34 — std::bind 회피
Abseil Code Review · 72 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::PeriodicSampler — 적응형 샘플링·jitter 회피
absl::profiling_internal::PeriodicSampler — sampling rate를 동적으로 조정, geometric distribution으로 jitter 회피. 메모리 할당 추적·profiling 인프라의 기반.
같은 시리즈에서 이어 읽기
absl::ComputeCrc32c — 하드웨어 가속 체크섬
absl::ComputeCrc32c — SSE4.2 CRC32, ARM CRC 명령어로 가속된 CRC32C 구현. iSCSI·Btrfs·protobuf에서 표준화된 무결성 검사.
같은 시리즈에서 이어 읽기
absl::GetStackTrace와 Symbolize — crash 시 readable stack
absl::GetStackTrace로 PC 배열을 받고 absl::Symbolize로 함수 이름·파일·라인으로 변환. signal handler 안에서도 동작하는 async-safe API.
같은 시리즈에서 이어 읽기