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

absl::StrCat — 가변 인자 문자열 연결과 AlphaNum

· Hawk · 3분 읽기

#한 줄 요약

absl::StrCat은 임의 개수 인자를 받아 단 한 번의 alloc으로 std::string을 만든다. 각 인자는 AlphaNum 어댑터를 거쳐 통일된 view로 환산되며, 미리 합계 길이를 계산한 뒤 buffer를 한 번만 reserve한다. operator+ 체인이나 ostringstream보다 빠른 이유는 여기에 있다.

#동기

std::stringoperator+는 좌결합으로 평가된다.

// 회피
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()...});
}

STLStringResizeUninitializedstd::string의 내부 버퍼를 0 초기화 없이 늘리는 트릭이다. 어차피 곧 덮어쓰기 때문에 zeroing 비용을 절약한다.

#std::string 비교

방식alloc 횟수비용
operator+ 체인N-1O(N²) 누적 복사
ostringstreamlog(N) (재할당)format state 머신
string::append 반복log(N) (재할당)좋음
StrCat1O(N) 복사, 가장 빠름

벤치마크 예(인자 10개, 짧은 문자열):

operator+ ~ 380 ns
ostringstream ~ 270 ns
StrCat ~ 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 등에서 유용하다.

// 회피 — 매 라인마다 alloc
for (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";
// 회피 — UB
s = absl::StrCat(s, "def", s);
// StrCat의 첫 인자에 들어간 `s`의 view가
// 결과 alloc 중 무효화될 수 있음
// Good — StrAppend
absl::StrAppend(&s, "def", s); // 이쪽도 위험 — Abseil 문서가 금지
// 정말 자기 자신을 추가하려면 임시 변수
std::string tmp = s;
absl::StrAppend(&s, "def", tmp);

Abseil 문서는 StrAppend(&s, ..., s, ...)도 명시적으로 금지한다.

#코드 리뷰 포인트

1. log 메시지의 << 체인을 StrCat으로

// 회피 — operator<< 호출이 매번 발생, 임시 stringstream
LOG(INFO) << "user=" << id << " ip=" << ip << " ms=" << ms;
// Good
LOG(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); // 컴파일 에러
// Good
std::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. 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 회피