absl::StrSplit — Delimiter·Predicate·컨테이너 변환
#한 줄 요약
absl::StrSplit은 문자열을 어떤 컨테이너로, 어떤 구분자로, 어떤 필터로 나눌지 세 축을 직교 분리한 표현식이다. 반환값은 implicit conversion으로 사용자가 원하는 컨테이너에 맞춰지고, Delimiter와 Predicate는 정책 객체로 따로 전달한다.
#동기
문자열 분리는 단순해 보이지만, 실제로는 변수의 조합이 많다.
- 구분자가 단일 char인지, 문자열인지, “셋 중 하나”인지
- 빈 토큰을 포함할지 버릴지
- 결과를
vector<string>으로 받을지,vector<string_view>로 받을지,set으로 받을지,pair로 받을지 - 토큰을 string_view로 다시 처리할지 string으로 복사할지
std::string의 find/substr 루프로 짜면 모든 조합을 매번 새로 작성해야 한다. StrSplit은 이를 표현식 한 줄로 압축한다.
#API와 사용법
#include "absl/strings/str_split.h"
// 단일 char 구분자, vector<string>std::vector<std::string> v = absl::StrSplit("a,b,c", ',');
// 단일 char 구분자, vector<string_view> (alloc 없음)std::vector<absl::string_view> sv = absl::StrSplit("a,b,c", ',');
// 문자열 구분자std::vector<std::string> v2 = absl::StrSplit("a::b::c", "::");// 또는 명시적으로std::vector<std::string> v3 = absl::StrSplit("a::b::c", absl::ByString("::"));
// "셋 중 하나"auto parts = absl::StrSplit("a,b;c:d", absl::ByAnyChar(",;:"));
// 고정 길이 토큰auto chunks = absl::StrSplit("abcdefgh", absl::ByLength(2));// {"ab","cd","ef","gh"}자동 컨테이너 변환은 implicit conversion으로 동작한다.
absl::flat_hash_set<std::string> uniq = absl::StrSplit("a,b,c,a", ',');std::map<std::string, std::string> kv = absl::StrSplit("k1=v1,k2=v2", ',');// kv는 ',' 분할 후 각 토큰을 또 '='로 분할? — 그건 다음 단락#Delimiter 종류
| Delimiter | 의미 |
|---|---|
char ',' | 단일 char (short-form) |
absl::ByChar(',') | 명시적 단일 char |
"::" (string literal) | 문자열 (short-form) |
absl::ByString("::") | 명시적 문자열 |
absl::ByAnyChar(",;:") | 문자 집합 중 아무거나 |
absl::ByLength(n) | 고정 길이 |
absl::MaxSplits(d, n) | 최대 n번 분할 |
absl::AllowEmpty() | 빈 토큰 유지(기본) |
auto a = absl::StrSplit("a:b:c:d", absl::MaxSplits(':', 2));// {"a", "b", "c:d"}MaxSplits는 path/URL 파싱에서 자주 쓰인다.
#Predicate 필터
세 번째 인자로 predicate를 넘기면 토큰 필터링이 된다.
absl::SkipEmpty(); // 빈 토큰 제거absl::SkipWhitespace(); // 공백만 있는 토큰까지 제거absl::AllowEmpty(); // 기본 (빈 토큰 포함)// 연속 공백 처리auto words = absl::StrSplit(" hello world ", ' ', absl::SkipEmpty());// {"hello", "world"}
// 람다 predicateauto longs = absl::StrSplit("a,bc,d,efg", ',', [](absl::string_view s) { return s.size() >= 2; });// {"bc", "efg"}predicate의 시그니처는 bool(absl::string_view)로 통일된다.
#pair / tuple 구조 분해
std::pair로 받으면 정확히 두 토큰을 기대한다.
std::pair<std::string, std::string> kv = absl::StrSplit("key=value", '=');// kv.first = "key", kv.second = "value"
// 토큰이 셋 이상이면 첫 둘만 채워지지 않고, 첫 토큰 + 나머지 join// — 동작이 모호하므로 권장하지 않음. 명시적 split 후 확인이 안전.key-value 짝을 map으로 받는 흔한 패턴.
std::map<std::string, std::string> config = absl::StrSplit("k1=v1,k2=v2,k3=v3", ',', absl::SkipEmpty());// 각 ',' 토큰이 std::pair<string,string>로 캐스팅되는 과정에서// 다시 '='로 split될 거라 기대하면 안 된다 — 두 단계가 필요.
absl::flat_hash_map<std::string, std::string> config2;for (absl::string_view kv : absl::StrSplit("k1=v1,k2=v2", ',')) { std::pair<std::string, std::string> p = absl::StrSplit(kv, absl::MaxSplits('=', 1)); config2.insert(std::move(p));}#내부 구현
StrSplit은 lazy iterator를 반환하는 view 객체다. implicit conversion 시점에 대상 컨테이너로 토큰이 흘러간다.
// absl/strings/str_split.h (요약)template <typename Delimiter>class Splitter { string_view text_; Delimiter delim_; Predicate pred_;
class iterator { /* string_view 토큰 생성 */ };
public: iterator begin() const; iterator end() const;
// 임의 컨테이너로 변환 template <typename Container> operator Container() const { Container c; for (string_view tok : *this) { if (pred_(tok)) c.insert(c.end(), Convert<value_type>(tok)); } return c; }};iterator는 매 step마다 delim_.Find()로 다음 구분 위치를 찾고, view를 잘라 반환한다. 토큰은 원본 텍스트의 view이므로 토큰 자체를 만드는 데에 alloc이 없다.
vector<string_view>로 받으면 컨테이너 alloc 한 번, 토큰 alloc 0. vector<string>으로 받으면 컨테이너 alloc + 토큰마다 string alloc.
#std::string 비교 — 직접 분리 루프
// 표준 라이브러리로 직접std::vector<std::string> Split(const std::string& s, char d) { std::vector<std::string> r; size_t start = 0, pos; while ((pos = s.find(d, start)) != std::string::npos) { r.emplace_back(s.substr(start, pos - start)); // alloc start = pos + 1; } r.emplace_back(s.substr(start)); // alloc return r;}토큰마다 substr alloc이 일어난다. StrSplit은 lazy view라 vector<string_view> 결과면 토큰 alloc이 전혀 없다.
C++20부터는 std::views::split이 있다. 표현은 비슷하나 구체 컨테이너 자동 변환이 약하고, 컴파일 에러가 길다. 가독성·성능 면에서 StrSplit이 여전히 앞선다.
#코드 리뷰 포인트
1. vector<string> 대신 vector<string_view>
후속 처리가 view로 충분하면 알로케이션 N번을 절약한다.
// 회피std::vector<std::string> parts = absl::StrSplit(line, ',');for (const auto& p : parts) if (p == "TARGET") found = true;
// Goodfor (absl::string_view p : absl::StrSplit(line, ',')) { if (p == "TARGET") { found = true; break; }}range-for 형태가 가장 가볍다. 컨테이너조차 만들지 않는다.
2. SkipEmpty 명시
CSV·log 파싱에서 trailing newline, 연속 구분자 등 빈 토큰이 흔하다. 명시적으로 처리한다.
for (absl::string_view line : absl::StrSplit(content, '\n', absl::SkipEmpty())) { Process(line);}3. MaxSplits로 분리 종료
// URL path 처리std::pair<absl::string_view, absl::string_view> p = absl::StrSplit(url, absl::MaxSplits('?', 1));absl::string_view path = p.first;absl::string_view query = p.second;#안티패턴
rvalue string 분리
StrSplit의 첫 인자는 string_view다. 토큰은 원본의 view다. 임시 std::string을 넘기면 임시는 소멸하고 토큰은 모두 dangling이 된다.
// 회피auto v = absl::StrSplit(LoadFile(), '\n'); // LoadFile() rvalue → 토큰 dangling
// Goodstd::string content = LoadFile();auto v = absl::StrSplit(content, '\n');Splitter는 임시 string에 대한 보조 생성자가 있어 컴파일은 통과하나, 토큰을 사용하기 전에 임시가 소멸할 수 있다. Abseil 문서가 명시 경고.
predicate 안에서 토큰 변경
predicate는 판정만 한다. 안에서 string으로 변환하거나 외부 상태를 바꾸면 lazy iteration이 어렵게 디버깅된다.
#정리
StrSplit은 Delimiter × Predicate × Container 세 축을 직교화한 표현식.- 토큰은 원본의 view, alloc은 컨테이너 변환 시점에만 발생.
ByChar,ByString,ByAnyChar,ByLength,MaxSplits로 Delimiter 선택.SkipEmpty,SkipWhitespace로 노이즈 제거.- 임시 string을 넘기면 토큰이 dangling이 된다.
#다음 편
Part 4-05 — StrJoin에서 반대 방향, 컨테이너를 한 string으로 합치는 표현식을 본다.
#관련 항목
Abseil Code Review · 23 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 Escape — CEscape·HexEscape·Base64
Part 4-08: absl::CEscape / CHexEscape / Base64Escape / WebSafeBase64Escape — 안전한 문자열 이스케이프와 base64 인코딩의 모든 변형.
같은 시리즈에서 이어 읽기
Abseil ASCII 함수 — locale-free 분류·대소문자 변환
Part 4-07: absl::ascii_* — locale 독립 ASCII 분류, AsciiStrToLower / AsciiStrToUpper / StripAsciiWhitespace.
같은 시리즈에서 이어 읽기