absl::StrCat — 가변 인자 문자열 연결과 AlphaNum
#한 줄 요약
absl::StrCat은 임의 개수 인자를 받아 단 한 번의 alloc으로 std::string을 만든다. 각 인자는 AlphaNum 어댑터를 거쳐 통일된 view로 환산되며, 미리 합계 길이를 계산한 뒤 buffer를 한 번만 reserve한다. operator+ 체인이나 ostringstream보다 빠른 이유는 여기에 있다.
#동기
std::string의 operator+는 좌결합으로 평가된다.
// 회피std::string r = a + b + c + d;// 실제: ((a + b) + c) + d// alloc 1: a+b → temp1// alloc 2: temp1+c → temp2// alloc 3: temp2+d → r각 단계마다 임시 string이 만들어진다. N개 인자에서 N-1번의 alloc이 발생하고, 누적 복사량은 O(N²)에 가깝다. ostringstream은 더 낫지만, locale/format state 머신 비용이 따른다.
StrCat은 이 비용을 한 번의 alloc + 선형 복사로 압축한다.
#API와 사용법
#include "absl/strings/str_cat.h"
namespace absl {template <typename... AV>std::string StrCat(const AV&... args);
template <typename... AV>void StrAppend(std::string* dest, const AV&... args);}기본 사용은 직관적이다.
std::string s = absl::StrCat("user=", user_id, " action=", action);// "user=42 action=login"
absl::StrAppend(&buf, " ", key, "=", value);지원 타입은 다음과 같다.
const char*,absl::string_view,std::string- 정수 타입(
int,int64_t, …), 부동소수점(float,double) bool(→ “0” 또는 “1”)char- 진법 어댑터:
absl::Hex,absl::Dec - 정밀도 어댑터:
absl::SixDigits(d)
absl::StrCat(absl::Hex(255)); // "ff"absl::StrCat(absl::Hex(255, absl::kZeroPad4)); // "00ff"absl::StrCat(absl::Hex(0xff, absl::kSpacePad6)); // " ff"absl::StrCat(absl::SixDigits(3.141592653589)); // "3.14159"#내부 구현 — AlphaNum
핵심은 absl::AlphaNum이라는 어댑터다. 모든 인자가 StrCat 본체로 들어가기 전 AlphaNum으로 변환된다.
// absl/strings/str_cat.h (요약)class AlphaNum { string_view piece_; char digits_[kFastToBufferSize]; // 정수/부동소수점 stringify 버퍼
public: AlphaNum(int x); AlphaNum(int64_t x); AlphaNum(double f); AlphaNum(absl::string_view sv) : piece_(sv) {} AlphaNum(const std::string& s) : piece_(s) {} AlphaNum(const char* c) : piece_(c) {} AlphaNum(Hex hex); // ...
string_view Piece() const { return piece_; } size_t size() const { return piece_.size(); }};정수 같은 비-문자열 타입은 생성자에서 즉시 stringify되어 내부 digits_ 버퍼에 저장되고, piece_는 그 버퍼를 가리키는 view가 된다. 이 변환은 스택에서 일어난다. heap alloc 없음.
StrCat의 본체는 단순하다.
// absl/strings/str_cat.cc (요약)std::string CatPieces(std::initializer_list<string_view> pieces) { // 1) 합계 길이 계산 size_t total = 0; for (string_view p : pieces) total += p.size();
// 2) 한 번에 reserve std::string result; STLStringResizeUninitialized(&result, total);
// 3) 순차 memcpy char* dst = &*result.begin(); for (string_view p : pieces) { if (!p.empty()) memcpy(dst, p.data(), p.size()); dst += p.size(); } return result;}
template <typename... AV>std::string StrCat(const AV&... args) { // 각 args를 AlphaNum으로 감싸 Piece()를 추출 return CatPieces({static_cast<const AlphaNum&>(args).Piece()...});}STLStringResizeUninitialized는 std::string의 내부 버퍼를 0 초기화 없이 늘리는 트릭이다. 어차피 곧 덮어쓰기 때문에 zeroing 비용을 절약한다.
#std::string 비교
| 방식 | alloc 횟수 | 비용 |
|---|---|---|
operator+ 체인 | N-1 | O(N²) 누적 복사 |
ostringstream | log(N) (재할당) | format state 머신 |
string::append 반복 | log(N) (재할당) | 좋음 |
StrCat | 1 | O(N) 복사, 가장 빠름 |
벤치마크 예(인자 10개, 짧은 문자열):
operator+ ~ 380 nsostringstream ~ 270 nsStrCat ~ 85 ns차이는 인자 개수가 늘수록 벌어진다.
#StrAppend
기존 string에 추가할 때는 StrAppend를 쓴다.
std::string log_line = "[INFO] ";absl::StrAppend(&log_line, "user=", user_id, " action=", action);내부 동작은 StrCat과 같다. 다만 결과 버퍼가 기존 string의 capacity 안에 들어가면 alloc 없이 끝난다. log accumulation 등에서 유용하다.
// 회피 — 매 라인마다 allocfor (const auto& e : entries) { buf = buf + Format(e) + "\n";}
// Good — 기존 buf 확장for (const auto& e : entries) { absl::StrAppend(&buf, Format(e), "\n");}#자가 참조 금지
StrCat 인자에 결과 변수를 직접 넣는 형태는 금지다.
std::string s = "abc";
// 회피 — UBs = absl::StrCat(s, "def", s);// StrCat의 첫 인자에 들어간 `s`의 view가// 결과 alloc 중 무효화될 수 있음
// Good — StrAppendabsl::StrAppend(&s, "def", s); // 이쪽도 위험 — Abseil 문서가 금지// 정말 자기 자신을 추가하려면 임시 변수std::string tmp = s;absl::StrAppend(&s, "def", tmp);Abseil 문서는 StrAppend(&s, ..., s, ...)도 명시적으로 금지한다.
#코드 리뷰 포인트
1. log 메시지의 << 체인을 StrCat으로
// 회피 — operator<< 호출이 매번 발생, 임시 stringstreamLOG(INFO) << "user=" << id << " ip=" << ip << " ms=" << ms;
// GoodLOG(INFO) << absl::StrCat("user=", id, " ip=", ip, " ms=", ms);단, glog/LOG은 stream 인터페이스가 표준이라 강요는 아니다. hot path만 변경한다.
2. 정수 to_string
// 회피std::string s = std::to_string(n); // alloc + format
// Good (동일 alloc, 더 빠른 코드 경로)std::string s = absl::StrCat(n);absl::StrCat(n)은 std::to_string보다 빠르다. AlphaNum이 스택 버퍼에서 itoa를 돌리고, locale을 무시하기 때문이다.
3. error message 조립
return absl::InvalidArgumentError( absl::StrCat("Expected ", expected, " got ", actual));absl::Status 메시지 조립의 표준 패턴이다.
#안티패턴
과도한 인자 수
StrCat은 한 호출에 최대 26개 인자까지 지원한다(Abseil 정의 한계). 그 이상이면 컴파일 에러. 분할해서 호출한다.
// 회피absl::StrCat(a, b, c, ..., z, aa); // 컴파일 에러
// Goodstd::string r = absl::StrCat(a, b, c, ..., z);absl::StrAppend(&r, aa);floating-point 정밀도
StrCat(double)은 6 significant digits로 출력한다. 더 필요하면 absl::StrFormat을 쓰거나 Part 4-06 참조.
#정리
StrCat은 모든 인자를AlphaNum으로 환산해 길이를 미리 계산한다.- 단 한 번의 alloc + 선형 memcpy로 완성된다.
operator+,ostringstream보다 빠르며 코드도 짧다.StrAppend는 기존 버퍼를 확장한다.- 자가 참조 인자는 금지.
#다음 편
Part 4-04 — StrSplit에서 반대 방향, 즉 문자열 분리의 일등 시민 API를 본다.
#관련 항목
Abseil Code Review · 22 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::string_view — non-owning 문자열 참조
Part 4-01: absl::string_view — 복사 없는 문자열 전달, lifetime 책임, std::string_view와의 관계.
같은 시리즈에서 이어 읽기
absl::PeriodicSampler — 적응형 샘플링·jitter 회피
absl::profiling_internal::PeriodicSampler — sampling rate를 동적으로 조정, geometric distribution으로 jitter 회피. 메모리 할당 추적·profiling 인프라의 기반.
같은 시리즈에서 이어 읽기
absl::from_chars·SimpleAtoi — 빠른 숫자 변환
absl::SimpleAtoi / SimpleAtof / from_chars — locale-free, exception-free, sscanf 대비 10~50배. std::charconv와의 관계.
같은 시리즈에서 이어 읽기
이 글을 참조하는 글 (10)
- absl::from_chars·SimpleAtoi — 빠른 숫자 변환 — Abseil Code Review
- absl::Cord — 분산 시스템용 대용량 문자열 — Abseil Code Review
- Abseil Flag introspection·validation — Abseil Code Review
- absl::int128·uint128 분석 — Abseil Code Review
- Abseil ASCII 함수 — locale-free 분류·대소문자 변환 — Abseil Code Review
- absl::StrFormat — type-safe printf·FormatSpec — Abseil Code Review
- absl::StrJoin — 컨테이너 결합과 Formatter — Abseil Code Review
- absl::StrSplit — Delimiter·Predicate·컨테이너 변환 — Abseil Code Review
- absl::string_view 함정 — dangling·c_str·임시 객체 — Abseil Code Review
- absl::string_view — non-owning 문자열 참조 — Abseil Code Review