absl::StrJoin — 컨테이너 결합과 Formatter
#한 줄 요약
absl::StrJoin은 임의 컨테이너를 임의 변환으로 한 구분자로 합치는 표현식이다. 기본 변환은 AlphaNum 호환이고, Formatter를 지정하면 원소를 임의 string으로 직렬화할 수 있다.
#동기
문자열 join은 StrSplit의 거울이다. log 한 줄, SQL IN (...), URL 쿼리스트링, CSV row 등 컨테이너를 텍스트로 묶는 일은 흔하다.
// 회피 — 매 iteration alloc + 마지막 구분자 처리std::string r;for (size_t i = 0; i < parts.size(); ++i) { if (i > 0) r += ","; r += parts[i];}StrJoin은 한 번의 alloc과 일관된 구분자 처리를 보장한다.
#API와 사용법
#include "absl/strings/str_join.h"
namespace absl {template <typename Container>std::string StrJoin(const Container& c, string_view sep);
template <typename Container, typename Formatter>std::string StrJoin(const Container& c, string_view sep, Formatter f);
template <typename Iter>std::string StrJoin(Iter first, Iter last, string_view sep);}기본 사용.
std::vector<std::string> v = {"a","b","c"};std::string s = absl::StrJoin(v, ", "); // "a, b, c"
std::vector<int> ns = {1,2,3};std::string s2 = absl::StrJoin(ns, "-"); // "1-2-3" (AlphaNum)
std::set<int> uniq = {3,1,4,1,5};std::string s3 = absl::StrJoin(uniq, ","); // "1,3,4,5" (정렬)
int arr[] = {10,20,30};std::string s4 = absl::StrJoin(arr, "|"); // "10|20|30"원소가 정수/부동소수점/string_view 같은 AlphaNum 호환 타입이면 별도 Formatter가 필요 없다.
#Formatter — 사용자 정의 직렬화
원소가 사용자 구조체이거나 특수 포맷이 필요하면 Formatter를 넘긴다. 시그니처는 void(std::string* out, const T& v)다.
struct User { int64_t id; std::string name; };std::vector<User> users = {{1,"Alice"}, {2,"Bob"}};
std::string s = absl::StrJoin(users, ", ", [](std::string* out, const User& u) { absl::StrAppend(out, u.name, "(", u.id, ")"); });// "Alice(1), Bob(2)"핵심은 appender다. 새 string을 반환하지 않고, 주어진 out에 StrAppend로 쓴다. 이로써 결과 버퍼는 한 번만 만들어진다.
#기본 제공 Formatter
absl::AlphaNumFormatter() // 기본 (생략 시 사용)absl::PairFormatter("=") // pair를 "k=v" 형식으로absl::DereferenceFormatter() // 포인터/optional 역참조absl::StreamFormatter() // ostream operator<< 호출// map → "k1=v1, k2=v2"absl::flat_hash_map<std::string,int> m = {{"a",1},{"b",2}};std::string s = absl::StrJoin(m, ", ", absl::PairFormatter("="));
// 포인터 컨테이너std::vector<std::unique_ptr<User>> ptrs = /*...*/;std::string s2 = absl::StrJoin(ptrs, ", ", absl::DereferenceFormatter( [](std::string* out, const User& u) { absl::StrAppend(out, u.name); }));#내부 구현
StrJoin은 두 단계로 동작한다.
// absl/strings/str_join.h (요약)template <typename Container, typename Formatter>std::string JoinAlgorithm(const Container& c, string_view sep, Formatter f) { std::string result; auto it = c.begin(); if (it != c.end()) { f(&result, *it); ++it; for (; it != c.end(); ++it) { result.append(sep.data(), sep.size()); f(&result, *it); } } return result;}원소를 미리 string으로 만들지 않는다. 매 step마다 Formatter가 result에 직접 append한다. 따라서 임시 string 객체가 생기지 않는다.
문자열 미리 합계 길이를 계산하지는 않는다(원소 string 길이를 알기 어렵기 때문). 대신 std::string::append의 amortized 재할당이 처리한다. result.reserve(estimate)를 미리 호출하면 더 좋다.
#std::string 비교
C++에는 표준 join이 없다. std::accumulate로 흉내낼 수 있으나 매번 임시 string을 만들거나 첫 구분자 처리를 수동으로 해야 한다. <ranges>의 views::join_with는 C++23부터.
#코드 리뷰 포인트
1. 수동 join 루프 → StrJoin
// 회피std::string ids;for (size_t i = 0; i < users.size(); ++i) { if (i > 0) ids += ","; ids += absl::StrCat(users[i].id);}
// Goodstd::string ids = absl::StrJoin(users, ",", [](std::string* out, const User& u) { absl::StrAppend(out, u.id); });2. SQL IN-clause 생성
std::string clause = absl::StrCat( "WHERE user_id IN (", absl::StrJoin(user_ids, ","), ")");값에 따옴표가 필요하면 Formatter로 감싼다.
auto quote = [](std::string* out, const std::string& s) { absl::StrAppend(out, "'", s, "'"); // 실제 SQL은 escape 필요};absl::StrJoin(names, ",", quote);(실제 SQL에는 escape/parameterized query를 써야 한다. 데모용.)
3. URL query string
absl::flat_hash_map<std::string, std::string> params = {/*...*/};std::string q = absl::StrJoin(params, "&", [](std::string* out, const auto& kv) { absl::StrAppend(out, kv.first, "=", kv.second); });absl::WebSafeBase64Escape를 적용해야 한다면 Part 4-08 참조.
#안티패턴
Formatter에서 새 string 반환
// 회피 — 매 호출 allocauto bad = [](const User& u) -> std::string { return absl::StrCat(u.name, "(", u.id, ")");};absl::StrJoin(users, ", ", [&](std::string* out, const User& u) { *out += bad(u); // 임시 string alloc});
// Goodauto good = [](std::string* out, const User& u) { absl::StrAppend(out, u.name, "(", u.id, ")");};appender 시그니처를 정확히 따른다.
huge container without reserve
수십만 개 원소면 result.reserve(approx)를 미리 호출해 재할당을 줄인다.
#정리
StrJoin은 임의 컨테이너 + 임의 변환 + 단일 구분자.- Formatter는
void(std::string*, const T&)appender. 새 string 반환 X. PairFormatter,DereferenceFormatter등 기본 제공.- map, set, vector, 배열, iterator 범위 모두 동일 인터페이스.
- 큰 컨테이너는
reserve로 재할당 절감.
#다음 편
Part 4-06 — StrFormat에서 printf 호환 type-safe 포맷팅을 본다.
#관련 항목
Abseil Code Review · 24 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 — 분산 시스템용 대용량 문자열
absl::Cord — tree 구조로 표현되는 immutable-ish 문자열. zero-copy concat, shared substring, Google 내부 RPC payload의 기본 표현.
같은 시리즈에서 이어 읽기
Abseil algorithm container 확장 — c_sort·c_find_if·c_count_if
absl::c_* algorithm wrapper — container 전체를 받아 begin/end 자동 처리, STL algorithm의 한 줄 boilerplate를 제거.
같은 시리즈에서 이어 읽기
absl::InlinedVector — small buffer optimization
Part 5-06: absl::InlinedVector — std::vector + 작으면 stack, 커지면 heap. SBO 패턴의 표준 도구.
같은 시리즈에서 이어 읽기