제목: “이번 주의 팁 #124: absl::StrFormat()
”
원문 게시일: 2016년 10월 11일
업데이트: 2022년 11월 16일
빠른 링크: abseil.io/tips/124
str_format
라이브러리와 absl::StrFormat()
오랜 테스트와 개발 과정을 거친 끝에, str_format
라이브러리를 정식으로 공개하게 되었습니다. str_format
라이브러리는 매우 효율적이고 타입 안전하며 확장 가능한 라이브러리로, 모든 printf
형식의 문법을 구현합니다. 거의 모든 printf
스타일 변환은 손쉽게 absl::StrFormat()
으로 업그레이드할 수 있습니다. 자세한 문서는 여기에서 확인하세요. 이 라이브러리는 printf 스타일 포맷팅에 가장 적합하지만, 이 글에서는 printf 스타일의 사용 여부에 대한 입장은 따로 표명하지 않습니다.
사용법
사용법은 간단합니다. //third_party/absl/strings:str_format
의존성을 추가하고 헤더를 포함하세요:
#include "absl/strings/str_format.h"
대부분의 사용자는 과거 StringPrintf()
나 util::format::StringF()
처럼 absl::StrFormat()
을 호출하는 방식으로 이 라이브러리를 사용할 수 있습니다. StrAppendFormat()
및 StreamFormat()
변형도 제공됩니다:
std::string s = absl::StrFormat("%d %s\n", 123, "hello");
타입 안전성과 C++의 장점
C 라이브러리의 printf()
와 달리, absl::StrFormat()
의 변환은 포맷 문자열에 정확한 타입을 지정하지 않아도 됩니다. printf()
에서는 %llu
와 같은 길이 수정자와 변환 지정자를 사용해 타입을 정확히 맞춰야 하지만, absl::StrFormat()
은 C++ 템플릿과 오버로딩을 활용해 호출자의 인자 타입을 안전하게 처리합니다.
예를 들어, %s
는 std::string
, absl::string_view
, Cord
, const char*
등 모든 문자열과 유사한 인자를 받아들입니다. 마찬가지로 %d
는 정수형과 호환되는 모든 타입을 수용합니다. 이런 특성은 유지보수에 매우 유리합니다:
X x = project_x::GetStats();
LOG(INFO) << absl::StreamFormat("%s:%08x", x.name, x.size);
여기서 name
은 문자열 유사 타입이라면 어떤 것이든 사용할 수 있고, size
는 정수형과 호환되는 모든 타입이 가능합니다.
출력 목적지 관리
str_format
라이브러리는 출력 목적지를 매우 부드럽게 제어할 수 있습니다. printf()
계열 함수에는 fprintf()
(FILE* 출력), sprintf()
(버퍼 출력), asprintf()
(메모리 할당 출력) 등 다양한 변형이 필요했지만, str_format
은 추상화된 싱크(sink)를 사용하므로 출력 목적지를 커스터마이즈하면서도 효율을 잃지 않습니다.
내장된 함수로는 다음과 같은 것들이 있습니다:
absl::StrFormat()
: 새로운std::string
생성absl::StrAppendFormat()
: 기존std::string
에 추가absl::StreamFormat()
:std::ostream
에 출력 (로그에 유용)
컴파일 타임 검증
Clang 컴파일러를 사용할 경우 리터럴 포맷 문자열에 대해 컴파일 타임 검증이 수행됩니다. 런타임에 포맷 문자열이 결정되는 경우에는 인자 리스트의 호환성을 미리 확인해야 합니다. 이를 통해 기존 printf()
의 위험 요소를 제거합니다. 또한 성능이 중요한 코드에서는 미리 포맷 사양을 파싱해 사용할 수 있어 성능 향상도 기대할 수 있습니다.
주요 차이점
str_format
라이브러리는 유연성을 유지하면서 정보 손실을 방지합니다. 예를 들어, 서명된 인자를 %u
나 %x
와 같은 부호 없는 변환자로 출력할 경우, 해당 값을 부호 없는 타입으로 변환한 뒤 출력합니다.
무엇보다도, 이 라이브러리는 최적화가 매우 잘 되어 있어 sprintf()
나 기존의 StringPrintf()
보다 훨씬 빠릅니다. (참고: [format-shootout] 결과)
예제
#include "absl/strings/str_format.h"
// 문자열 추가 예제
absl::StrAppendFormat(&s, "또한, %s\n", epilogue);
// 로그 출력 예제
for (const auto& g : hard_workers)
LOG(INFO) << absl::StreamFormat("%-20s %8.2f", g.username, g.bonus);
// POSIX 위치 지정자 예제
summary = absl::StrFormat("%2$s, %3$s, %1$s!", "vici", "veni", "vidi");
// 런타임 포맷 문자열 예제
std::string letter = response.format_string();
auto format = absl::ParsedFormat<'d', 's', 's'>::New(letter);
if (!format) {
return;
}
letter = StringF(*format, vacation_days, from, to);
// 성능 최적화를 위한 사전 포맷
static const auto* const pfmt = new absl::ParsedFormat<'s','d'>(
"<tr><td>%s</td><td>%08d</td></tr>\n");
for (const auto& joe : folks) {
absl::StrAppendFormat(&output, *pfmt, joe.name, joe.id);
}
// ostream 포맷 예제
s = absl::StrFormat("[%-12s]", absl::FormatStreamed(x));
더 빠르고 타입 안전한 absl::StrFormat()
을 적극적으로 사용해 보세요!