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

absl::function_ref와 any_invocable — 함수 객체 전달의 두 축

· Hawk · 4분 읽기

#한 줄 요약

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 가능 강제 + alloc
void ForEach(std::function<void(int)> p);

std::functionstd::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::function
class 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_refany_invocablestd::functionstd::move_only_function (C++23)
소유×OOO
copy×O×
moveOOO
비용16B view24B SBO + alloc32B SBO + alloc32B SBO + alloc
const/noexcept qualifier×O×O
표준C++11C++23

#코드 리뷰 포인트

1. “호출만” 매개변수면 FunctionRef

// 회피 — alloc + copy 강제
void Each(std::function<void(int)> visit);
// Good
void Each(absl::FunctionRef<void(int)> visit);

Each가 callback을 저장하지 않고 호출만 한다면 alloc할 이유가 없다.

2. 저장은 AnyInvocable, std::function 권장 회피

// 회피 — copy 강제 + 큰 SBO
class TaskQueue {
std::vector<std::function<void()>> queue_;
};
// Good — move-only OK, 작은 SBO
class 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, noexcept qualifier를 시그니처에 인코딩하지 못한다.

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::functionFunctionRef로 받아 복사 회피 의도

// 호출자는 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에서 보조 함수 객체 도구를 본다.

#관련 항목

Abseil Code Review · 72 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 회피