C++20에서 도입된 <format>
라이브러리는 기존의 printf
및 sstream
방식보다 안전하고 간결한 문자열 포매팅을 제공합니다. 기존 방식인 printf
는 타입 불일치 문제와 버퍼 오버플로우 위험이 있으며, sstream
은 상대적으로 복잡한 문법을 요구합니다.
이에 반해, <format>
라이브러리는 타입 안정성을 유지하면서도 가독성이 뛰어난 방식으로 문자열을 포매팅할 수 있습니다. 또한, std::format
은 C++ 스타일의 함수형 포매팅을 제공하여 컴파일 타임에서 오류를 감지할 수 있어 더 안전한 프로그래밍이 가능합니다.
본 기사에서는 <format>
의 기본적인 사용법부터 서식 지정자 활용법, 성능 비교, 실전 활용 예제까지 다루며, C++20을 활용한 효율적인 문자열 처리 방법을 상세히 살펴보겠습니다.
C++20 format 라이브러리란?
C++20에서 새롭게 도입된 <format>
라이브러리는 문자열을 안전하고 효율적으로 포매팅할 수 있도록 설계된 표준 라이브러리입니다. 이는 기존의 printf
기반 포매팅 방식보다 더 직관적이고 안전한 방법을 제공합니다.
기존 C++에서는 문자열을 포매팅하기 위해 printf
, std::stringstream
, std::to_string
같은 다양한 방법을 사용해야 했습니다. 하지만 <format>
은 타입 안정성을 보장하면서도 가독성이 뛰어난 문법을 제공하여, 기존 방식보다 더 쉽게 사용할 수 있습니다.
주요 특징
- 안전성:
printf
처럼 포맷 문자열과 인수 타입이 불일치하는 오류를 방지 - 간결한 문법:
std::format("Hello, {}!", "World")
처럼 직관적인 사용 가능 - 성능 최적화: 기존
sstream
보다 성능이 우수 - 타입 유연성: 정수, 실수, 문자열뿐만 아니라 사용자 정의 타입도 포맷 가능
- 로캘(Localization) 지원: 다국어 및 통화 형식 지원
예제 코드
#include <iostream>
#include <format>
int main() {
std::string message = std::format("Hello, {}!", "World");
std::cout << message << std::endl; // 출력: Hello, World!
}
위와 같이 <format>
을 사용하면 가독성이 뛰어나면서도 안전한 문자열 포매팅이 가능합니다. 다음 절에서는 기존 문자열 포매팅 방식의 문제점과 <format>
이 해결하는 점에 대해 살펴보겠습니다.
기존 문자열 포매팅 방식의 문제점
C++에서 문자열을 포매팅하는 기존 방식에는 printf
기반 포맷팅과 std::stringstream
방식이 대표적입니다. 하지만 이들은 여러 가지 문제점을 가지고 있으며, C++20의 <format>
라이브러리는 이러한 문제를 해결하기 위해 도입되었습니다.
1. printf
의 한계
printf
는 C 언어 시절부터 사용되던 포매팅 방식으로, 가볍고 빠르지만 안전성이 떨어지는 문제점이 있습니다.
#include <cstdio>
int main() {
printf("정수: %d, 실수: %f, 문자열: %s\n", 42, 3.14, "Hello");
return 0;
}
주요 문제점
- 타입 불일치 문제:
printf("정수: %d\n", 3.14); // 잘못된 사용 (컴파일 오류 없음, 런타임 오류 가능)
printf
는 타입 검사를 하지 않기 때문에 float
을 %d
로 출력하면 잘못된 결과가 발생할 수 있습니다.
- 버퍼 오버플로우 위험:
char buffer[10];
sprintf(buffer, "숫자: %d", 123456789); // 버퍼 크기 초과 → 버퍼 오버플로우 발생 가능
printf
는 정적인 포맷 문자열과 가변적인 데이터를 처리할 때 버퍼 오버플로우 위험이 있습니다.
2. std::stringstream
의 한계
std::stringstream
은 C++에서 타입 안정성을 보장하는 문자열 포매팅 방법이지만, 코드가 복잡해지는 단점이 있습니다.
#include <iostream>
#include <sstream>
int main() {
std::stringstream ss;
ss << "정수: " << 42 << ", 실수: " << 3.14 << ", 문자열: " << "Hello";
std::cout << ss.str() << std::endl;
}
주요 문제점
- 번거로운 문법:
<<
연산자를 계속 사용해야 하므로 가독성이 떨어짐 - 성능 문제: 내부적으로 동적 메모리를 할당하여 처리하므로
printf
보다 느림 - 서식 지정이 어려움: 숫자의 소수점 자리수를 지정하는 등 세밀한 제어가 불편함
<format>
라이브러리의 해결책
C++20 <format>
은 위의 문제를 해결하면서도 직관적인 문법을 제공합니다.
#include <iostream>
#include <format>
int main() {
std::string result = std::format("정수: {}, 실수: {:.2f}, 문자열: {}", 42, 3.14, "Hello");
std::cout << result << std::endl;
}
개선된 점
✅ 타입 안전성: 잘못된 타입을 사용하면 컴파일러가 오류를 발생시킴
✅ 버퍼 오버플로우 방지: 동적 할당을 자동으로 처리하여 메모리 안전성 보장
✅ 간결한 문법: {}
플레이스홀더를 사용해 가독성을 향상
이제, 다음 절에서는 <format>
의 기본적인 사용법을 살펴보겠습니다.
기본적인 std::format
사용법
C++20의 <format>
라이브러리는 {}
플레이스홀더를 이용하여 직관적이고 안전한 문자열 포매팅을 제공합니다. 기존 printf
와 달리 타입 안정성이 보장되며, 가독성이 뛰어납니다.
std::format
의 기본 문법
#include <iostream>
#include <format>
int main() {
std::string result = std::format("Hello, {}!", "World");
std::cout << result << std::endl; // 출력: Hello, World!
}
✅ {}
안에 들어갈 값은 자동으로 타입이 매칭됩니다.
✅ C++ 스타일의 안전한 문자열 포매팅이 가능하며, std::string
을 반환합니다.
여러 개의 값 포매팅
다수의 변수를 {}
안에 순서대로 배치할 수 있습니다.
#include <iostream>
#include <format>
int main() {
std::string message = std::format("{} scored {} points in {} seconds", "Alice", 95, 30.5);
std::cout << message << std::endl;
}
출력 결과:
Alice scored 95 points in 30.5 seconds
✅ std::format
은 자동으로 타입을 감지하며, 정수(int
), 실수(double
), 문자열(std::string
) 모두 {}
안에서 사용 가능합니다.
✅ 타입 불일치 오류가 발생하지 않으며, printf
처럼 잘못된 형식을 지정해도 런타임 오류가 발생하지 않습니다.
위치 지정 인수
C++20 <format>
은 {}
에 위치 인덱스를 부여하여 순서를 변경할 수도 있습니다.
#include <iostream>
#include <format>
int main() {
std::string text = std::format("{1} scored {0} points", 95, "Alice");
std::cout << text << std::endl;
}
출력 결과:
Alice scored 95 points
✅ {n}
형식을 사용하면 순서를 조정할 수 있어 유연성이 증가합니다.
숫자 형식 지정
std::format
은 {}
안에 서식 지정자를 포함하여 정수와 실수의 표현을 제어할 수 있습니다.
#include <iostream>
#include <format>
int main() {
std::cout << std::format("정수: {:06}\n", 42); // 6자리 정수, 빈칸은 0으로 채움
std::cout << std::format("소수점: {:.2f}\n", 3.14159); // 소수점 2자리까지 출력
std::cout << std::format("16진수: {:#x}\n", 255); // 16진수(hex) 출력
}
출력 결과:
정수: 000042
소수점: 3.14
16진수: 0xff
✅ {:06}
→ 숫자 앞을 0
으로 채우고 6자리 고정 출력
✅ {:.2f}
→ 소수점 2자리까지만 출력
✅ {:#x}
→ 16진수(hex) 포맷
문자열 정렬
std::format
을 사용하면 문자열을 왼쪽, 오른쪽, 중앙 정렬할 수 있습니다.
#include <iostream>
#include <format>
int main() {
std::cout << std::format("[{:>10}]\n", "Right"); // 오른쪽 정렬 (10자리)
std::cout << std::format("[{:<10}]\n", "Left"); // 왼쪽 정렬 (10자리)
std::cout << std::format("[{:^10}]\n", "Center"); // 중앙 정렬 (10자리)
}
출력 결과:
[ Right]
[Left ]
[ Center ]
✅ {:<10}
→ 왼쪽 정렬
✅ {:>10}
→ 오른쪽 정렬
✅ {:^10}
→ 중앙 정렬
요약
✅ std::format
은 {}
플레이스홀더를 사용하여 안전하고 간결한 문자열 포매팅을 지원
✅ 타입 불일치 오류를 컴파일 타임에서 감지하여 안전성 향상
✅ 숫자 형식 지정, 문자열 정렬, 위치 지정 인수 등을 지원
✅ 기존 printf
및 stringstream
보다 가독성과 성능이 뛰어남
다음 절에서는 서식 지정자를 활용하여 포매팅을 더욱 세밀하게 제어하는 방법을 알아보겠습니다.
서식 지정자 활용하기
C++20 <format>
라이브러리는 서식 지정자를 사용하여 숫자, 문자열, 날짜 등의 출력 형식을 세밀하게 조정할 수 있습니다. 이를 통해 소수점 자리수, 패딩, 정렬, 진법 변환 등을 간편하게 처리할 수 있습니다.
숫자 서식 지정
1. 정수 포매팅
정수를 출력할 때 자릿수 맞추기, 진법 변환, 기호 포함 등의 조정이 가능합니다.
#include <iostream>
#include <format>
int main() {
std::cout << std::format("10진수: {}\n", 255);
std::cout << std::format("16진수: {:#x}\n", 255); // #을 붙이면 '0x' 포함
std::cout << std::format("8진수: {:#o}\n", 255); // #을 붙이면 '0o' 포함
std::cout << std::format("2진수: {:#b}\n", 255); // #을 붙이면 '0b' 포함
}
출력 결과:
10진수: 255
16진수: 0xff
8진수: 0o377
2진수: 0b11111111
✅ {:#x}
→ 16진수 (0x
포함)
✅ {:#o}
→ 8진수 (0o
포함)
✅ {:#b}
→ 2진수 (0b
포함)
2. 숫자 패딩 및 정렬
숫자의 길이를 일정하게 맞추기 위해 공백, 0 채우기, 정렬을 사용할 수 있습니다.
#include <iostream>
#include <format>
int main() {
std::cout << std::format("기본 출력: [{}]\n", 42);
std::cout << std::format("오른쪽 정렬: [{:6}]\n", 42);
std::cout << std::format("왼쪽 정렬: [{:<6}]\n", 42);
std::cout << std::format("중앙 정렬: [{:^6}]\n", 42);
std::cout << std::format("0 채우기: [{:06}]\n", 42);
}
출력 결과:
기본 출력: [42]
오른쪽 정렬: [ 42]
왼쪽 정렬: [42 ]
중앙 정렬: [ 42 ]
0 채우기: [000042]
✅ {:6}
→ 6자리 공간 확보, 오른쪽 정렬 (기본값)
✅ {:<6}
→ 왼쪽 정렬
✅ {:^6}
→ 중앙 정렬
✅ {:06}
→ 빈 칸을 0으로 채우기
실수 서식 지정
1. 소수점 자리수 지정
#include <iostream>
#include <format>
int main() {
std::cout << std::format("기본 출력: {}\n", 3.14159);
std::cout << std::format("소수점 2자리: {:.2f}\n", 3.14159);
std::cout << std::format("소수점 4자리: {:.4f}\n", 3.14159);
}
출력 결과:
기본 출력: 3.14159
소수점 2자리: 3.14
소수점 4자리: 3.1416
✅ {:.2f}
→ 소수점 이하 2자리까지 표시
✅ {:.4f}
→ 소수점 이하 4자리까지 표시 (반올림 적용)
2. 지수 표기법
#include <iostream>
#include <format>
int main() {
std::cout << std::format("일반 출력: {}\n", 12345.6789);
std::cout << std::format("지수 표기: {:.2e}\n", 12345.6789);
}
출력 결과:
일반 출력: 12345.6789
지수 표기: 1.23e+04
✅ {:.2e}
→ 지수 표기법 사용 (e
형식)
문자열 서식 지정
1. 문자열 길이 제한
#include <iostream>
#include <format>
int main() {
std::cout << std::format("[{:.5}]\n", "Hello, World!"); // 앞에서 5글자까지만 출력
}
출력 결과:
[Hello]
✅ {:.5}
→ 최대 5글자까지만 출력
2. 문자열 정렬
#include <iostream>
#include <format>
int main() {
std::cout << std::format("[{:>10}]\n", "Right");
std::cout << std::format("[{:<10}]\n", "Left");
std::cout << std::format("[{:^10}]\n", "Center");
}
출력 결과:
[ Right]
[Left ]
[ Center ]
✅ {:<10}
→ 왼쪽 정렬
✅ {:>10}
→ 오른쪽 정렬
✅ {:^10}
→ 중앙 정렬
조합 활용 예제
여러 서식 지정자를 조합하여 가독성 높은 출력 형식을 만들 수 있습니다.
#include <iostream>
#include <format>
int main() {
std::cout << std::format("|{:^10}|{:>10}|{:06}|\n", "Text", 42, 7);
std::cout << std::format("|{:#08x}|{:.2f}|\n", 255, 3.14159);
}
출력 결과:
| Text | 42|000007|
|0x0000ff|3.14|
✅ {:^10}
→ 문자열을 10자리 중앙 정렬
✅ {:>10}
→ 숫자를 10자리 오른쪽 정렬
✅ {:06}
→ 숫자를 6자리 0 패딩
✅ {:#08x}
→ 8자리 16진수, 0x 포함, 앞을 0으로 채움
✅ {:.2f}
→ 소수점 이하 2자리까지 출력
요약
✅ 정수: {:#x}
, {:#o}
, {:#b}
→ 진법 변환 지원
✅ 실수: {:.2f}
, {:.2e}
→ 소수점 자리수 및 지수 표기 지원
✅ 문자열: {:<10}
, {:>10}
, {:^10}
→ 정렬 기능 제공
✅ 숫자 패딩: {:06}
, {:#08x}
→ 고정 길이 숫자 출력 가능
다음 절에서는 로캘을 활용한 다국어 및 통화 형식 지원 방법을 살펴보겠습니다.
std::format
과 로캘 지원
C++20 <format>
라이브러리는 다국어 및 통화 형식을 지원하기 위해 로캘(locale) 기능을 제공합니다. 이를 통해 숫자 및 통화 단위를 특정 국가 및 언어 환경에 맞게 변환할 수 있습니다.
1. 로캘(locale) 개념
로캘(Locale)은 국가별 표기법을 지정하는 설정입니다. 예를 들어, 미국과 독일에서 숫자 및 통화 형식은 다르게 표시됩니다.
로캘 | 1000000의 표기법 |
---|---|
미국 (en_US) | 1,000,000 |
독일 (de_DE) | 1.000.000 |
프랑스 (fr_FR) | 1 000 000 |
C++20 <format>
은 std::locale
을 사용하여 이러한 국가별 표기법을 적용할 수 있습니다.
2. 로캘을 적용한 숫자 포매팅
기본 숫자 포매팅 (로캘 적용 전)
기본적으로 std::format
은 로캘을 적용하지 않고 숫자를 출력합니다.
#include <iostream>
#include <format>
int main() {
std::cout << std::format("숫자 출력 (기본): {:n}\n", 1000000);
}
출력 결과 (로캘 적용 전):
숫자 출력 (기본): 1000000
✅ {n}
→ 숫자 형식 출력을 의미하지만, 기본적으로 로캘을 적용하지 않음
3. std::locale
을 사용한 숫자 형식 변환
C++에서 std::locale
을 사용하여 숫자 포맷을 변경할 수 있습니다.
#include <iostream>
#include <format>
#include <locale>
int main() {
std::locale::global(std::locale("en_US.UTF-8")); // 미국 로캘 설정
std::cout << std::format(std::locale("en_US.UTF-8"), "미국 표기법: {:n}\n", 1000000);
std::cout << std::format(std::locale("de_DE.UTF-8"), "독일 표기법: {:n}\n", 1000000);
}
출력 결과:
미국 표기법: 1,000,000
독일 표기법: 1.000.000
✅ std::locale("en_US.UTF-8")
→ 미국 숫자 포맷 적용 (1,000,000
)
✅ std::locale("de_DE.UTF-8")
→ 독일 숫자 포맷 적용 (1.000.000
)
⚠️ 주의: 실행 환경에 따라 특정 로캘이 지원되지 않을 수도 있으므로 확인이 필요합니다.
4. 통화(currency) 포맷 적용
C++20 <format>
은 통화 포맷을 지원하지 않지만, std::put_money
를 사용하면 특정 국가의 통화 형식을 적용할 수 있습니다.
#include <iostream>
#include <iomanip>
#include <locale>
int main() {
std::locale::global(std::locale("en_US.UTF-8")); // 미국 로캘 설정
std::cout.imbue(std::locale("en_US.UTF-8"));
std::cout << "미국 달러: " << std::showbase << std::put_money(1234567) << std::endl;
std::cout.imbue(std::locale("de_DE.UTF-8"));
std::cout << "독일 유로: " << std::showbase << std::put_money(1234567) << std::endl;
}
출력 결과:
미국 달러: $12,345.67
독일 유로: 12.345,67 €
✅ std::put_money(1234567)
→ 통화 포맷 적용 (소수점 2자리 자동 계산)
✅ std::locale("de_DE.UTF-8")
→ 독일 유로(€) 적용
✅ std::locale("en_US.UTF-8")
→ 미국 달러($) 적용
5. 날짜 및 시간 포맷
로캘을 적용하여 날짜 및 시간을 포맷할 수도 있습니다.
#include <iostream>
#include <format>
#include <chrono>
#include <locale>
int main() {
auto now = std::chrono::system_clock::now();
std::cout << std::format(std::locale("en_US.UTF-8"), "미국 날짜 형식: {:%A, %B %d, %Y}\n", now);
std::cout << std::format(std::locale("de_DE.UTF-8"), "독일 날짜 형식: {:%A, %d. %B %Y}\n", now);
}
출력 결과 (예시):
미국 날짜 형식: Wednesday, January 31, 2025
독일 날짜 형식: Mittwoch, 31. Januar 2025
✅ {:%A, %B %d, %Y}
→ 요일, 월, 일, 연도를 포맷
✅ std::locale("en_US.UTF-8")
→ 미국 스타일 날짜 형식
✅ std::locale("de_DE.UTF-8")
→ 독일 스타일 날짜 형식
요약
✅ std::locale
을 사용하여 숫자, 통화, 날짜 형식을 다국어 지원 가능
✅ {n}
서식 지정자로 숫자 포맷을 국가별 표기법으로 변경 가능
✅ std::put_money
를 사용하여 통화 형식 적용 가능
✅ {:%A, %B %d, %Y}
형식을 사용하여 날짜/시간 포맷 적용 가능
다음 절에서는 사용자 정의 타입을 std::format
에서 활용하는 방법을 알아보겠습니다.
사용자 정의 타입 포매팅
C++20의 <format>
라이브러리는 기본 제공되는 정수, 실수, 문자열 타입뿐만 아니라 사용자 정의 타입도 포매팅할 수 있도록 확장할 수 있습니다. 이를 위해 std::formatter
템플릿을 오버라이드하여 특정 구조체나 클래스를 포맷할 수 있습니다.
1. 기본 사용자 정의 타입 포매팅
사용자 정의 타입을 std::format
에서 사용하려면 std::formatter<T>
를 특수화해야 합니다.
예제: Point
구조체의 포매팅
#include <iostream>
#include <format>
struct Point {
int x, y;
};
// 사용자 정의 타입에 대한 포매팅 지원
template <>
struct std::formatter<Point> {
constexpr auto parse(std::format_parse_context& ctx) { return ctx.begin(); }
template <typename FormatContext>
auto format(const Point& p, FormatContext& ctx) {
return std::format_to(ctx.out(), "({}, {})", p.x, p.y);
}
};
int main() {
Point p{3, 7};
std::cout << std::format("Point: {}\n", p); // 사용자 정의 타입 포매팅 적용
}
출력 결과:
Point: (3, 7)
✅ std::formatter<Point>
를 특수화하여 사용자 정의 포맷 방식을 제공
✅ format_to(ctx.out(), "({}, {})", p.x, p.y)
를 사용하여 Point
객체를 (x, y)
형식으로 출력
2. 여러 형식을 지원하는 사용자 정의 포매팅
사용자 정의 포매터에서 포맷 문자열을 해석하여 여러 포맷 옵션을 제공할 수도 있습니다.
예제: RGB 색상 클래스
#include <iostream>
#include <format>
struct Color {
int r, g, b;
};
// 사용자 정의 포매팅: 16진수, RGB 형식 지원
template <>
struct std::formatter<Color> {
char presentation = 'd'; // 기본은 10진수
constexpr auto parse(std::format_parse_context& ctx) {
auto it = ctx.begin();
if (it != ctx.end() && (*it == 'd' || *it == 'x')) {
presentation = *it;
++it;
}
return it;
}
template <typename FormatContext>
auto format(const Color& c, FormatContext& ctx) {
if (presentation == 'x') {
return std::format_to(ctx.out(), "#{:02X}{:02X}{:02X}", c.r, c.g, c.b);
} else {
return std::format_to(ctx.out(), "rgb({}, {}, {})", c.r, c.g, c.b);
}
}
};
int main() {
Color c{255, 100, 50};
std::cout << std::format("Default: {}\n", c); // 기본 RGB 형식
std::cout << std::format("Hex: {:x}\n", c); // 16진수 형식
}
출력 결과:
Default: rgb(255, 100, 50)
Hex: #FF6432
✅ std::formatter<Color>
에서 parse()
를 오버라이드하여 d
(10진수)와 x
(16진수) 옵션을 지원
✅ format()
에서 {:x}
옵션을 사용하면 #RRGGBB
16진수 형식으로 변환
3. 클래스 멤버 변수 포매팅
객체의 여러 멤버 변수를 포매팅할 수도 있습니다.
예제: Person
클래스
#include <iostream>
#include <format>
struct Person {
std::string name;
int age;
};
// 사용자 정의 포매팅
template <>
struct std::formatter<Person> {
constexpr auto parse(std::format_parse_context& ctx) { return ctx.begin(); }
template <typename FormatContext>
auto format(const Person& p, FormatContext& ctx) {
return std::format_to(ctx.out(), "{} ({} years old)", p.name, p.age);
}
};
int main() {
Person person{"Alice", 30};
std::cout << std::format("Person: {}\n", person);
}
출력 결과:
Person: Alice (30 years old)
✅ 객체의 여러 멤버 변수를 조합하여 포맷 가능
4. JSON 스타일 포매팅
사용자 정의 객체를 JSON 스타일로 출력할 수도 있습니다.
#include <iostream>
#include <format>
struct Book {
std::string title;
std::string author;
int year;
};
// JSON 스타일 포매팅 지원
template <>
struct std::formatter<Book> {
constexpr auto parse(std::format_parse_context& ctx) { return ctx.begin(); }
template <typename FormatContext>
auto format(const Book& b, FormatContext& ctx) {
return std::format_to(ctx.out(),
"{{ \"title\": \"{}\", \"author\": \"{}\", \"year\": {} }}",
b.title, b.author, b.year);
}
};
int main() {
Book book{"1984", "George Orwell", 1949};
std::cout << std::format("Book Info: {}\n", book);
}
출력 결과:
Book Info: { "title": "1984", "author": "George Orwell", "year": 1949 }
✅ JSON 스타일로 객체 데이터를 출력할 수 있도록 std::formatter<Book>
를 확장
요약
✅ std::formatter<T>
를 특수화하면 사용자 정의 타입을 std::format
에서 활용 가능
✅ std::format_to()
를 사용하여 객체의 멤버 변수를 원하는 형식으로 변환
✅ parse()
를 오버라이드하여 다양한 출력 형식 (d
, x
, JSON 등) 지원 가능
✅ std::format("{:x}", object)
처럼 다양한 포맷 옵션을 객체에도 적용 가능
다음 절에서는 std::format
의 성능과 메모리 효율성을 printf
, sstream
과 비교하여 알아보겠습니다.
성능과 메모리 효율성 비교
C++20 <format>
라이브러리는 기존 문자열 포매팅 방식(printf
, std::stringstream
)과 비교하여 안전성뿐만 아니라 성능과 메모리 사용량에서도 뛰어난 장점을 가집니다. 이번 절에서는 <format>
과 기존 방식의 성능을 비교하고, 메모리 효율성에 대해 분석합니다.
1. 성능 비교 테스트
테스트 코드: printf
, std::stringstream
, std::format
성능 비교
다음 코드에서는 동일한 포매팅 작업을 printf
, std::stringstream
, std::format
을 사용하여 실행 시간을 측정합니다.
#include <iostream>
#include <format>
#include <sstream>
#include <cstdio>
#include <chrono>
void test_printf(int iterations) {
char buffer[100];
for (int i = 0; i < iterations; i++) {
std::sprintf(buffer, "Number: %d, Float: %.2f, String: %s", 42, 3.14159, "Hello");
}
}
void test_sstream(int iterations) {
for (int i = 0; i < iterations; i++) {
std::stringstream ss;
ss << "Number: " << 42 << ", Float: " << 3.14159 << ", String: " << "Hello";
std::string result = ss.str();
}
}
void test_format(int iterations) {
for (int i = 0; i < iterations; i++) {
std::string result = std::format("Number: {}, Float: {:.2f}, String: {}", 42, 3.14159, "Hello");
}
}
int main() {
constexpr int iterations = 1'000'000;
auto start = std::chrono::high_resolution_clock::now();
test_printf(iterations);
auto end = std::chrono::high_resolution_clock::now();
std::cout << "printf time: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()
<< " ms\n";
start = std::chrono::high_resolution_clock::now();
test_sstream(iterations);
end = std::chrono::high_resolution_clock::now();
std::cout << "stringstream time: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()
<< " ms\n";
start = std::chrono::high_resolution_clock::now();
test_format(iterations);
end = std::chrono::high_resolution_clock::now();
std::cout << "std::format time: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()
<< " ms\n";
return 0;
}
성능 비교 결과 (예상)
포매팅 방식 | 실행 시간 (ms) |
---|---|
printf | 80 ms |
std::stringstream | 320 ms |
std::format | 100 ms |
✅ printf
는 빠르지만 안전성이 부족하며 버퍼 크기 조정이 필요
✅ std::stringstream
은 가독성이 좋지만 속도가 느림 (동적 할당 및 문자열 조작)
✅ std::format
은 printf
보다 약간 느리지만, std::stringstream
보다 훨씬 빠름
2. 메모리 효율성 비교
각 방식에서 메모리 할당 및 사용량을 비교하면 다음과 같습니다.
포매팅 방식 | 메모리 사용량 | 주요 특징 |
---|---|---|
printf | 낮음 | 정적 버퍼 사용 (수동 관리 필요) |
std::stringstream | 높음 | 내부적으로 동적 메모리 할당 수행 |
std::format | 낮음 | 메모리 사용량이 효율적이며, std::string 과 잘 통합 |
✅ std::format
은 동적 메모리 할당이 최소화되어 std::stringstream
보다 메모리 효율성이 높음
✅ printf
는 메모리 사용량이 적지만, 버퍼 오버플로우 위험이 있음
✅ std::stringstream
은 메모리 사용량이 많고 성능이 가장 느림
3. 실제 애플리케이션에서의 장점
✅ printf
대비 장점
비교 항목 | printf | std::format |
---|---|---|
안전성 | 포맷 문자열 오류 가능 (컴파일러 검출 불가) | 컴파일 타임에서 검출 가능 |
유형 안정성 | 데이터 타입이 맞지 않아도 오류 발생 안 함 | 데이터 타입이 다르면 컴파일 오류 |
가독성 | %d , %f 등 서식 지정 필요 | {} 를 사용한 직관적 포매팅 |
예제:
std::cout << std::format("Hello, {}!", "World"); // 안전 (컴파일 체크 가능)
printf("Hello, %s!\n", 42); // 오류 발생 가능 (컴파일러 체크 불가)
✅ std::stringstream
대비 장점
비교 항목 | std::stringstream | std::format |
---|---|---|
성능 | 느림 (동적 메모리 할당) | 빠름 |
가독성 | << 연산자로 복잡한 코드 | {} 를 사용하여 직관적 |
메모리 사용 | 내부적으로 동적 메모리 관리 | 메모리 사용량이 적음 |
예제:
// std::stringstream 방식
std::stringstream ss;
ss << "Value: " << 42 << ", PI: " << 3.14159;
std::cout << ss.str();
// std::format 방식 (더 간결하고 빠름)
std::cout << std::format("Value: {}, PI: {:.2f}", 42, 3.14159);
✅ std::format
은 코드가 더 간결하며 실행 속도가 빠름
✅ std::stringstream
은 느리고 메모리 사용량이 많음
4. 요약
✅ std::format
은 printf
보다 안전하고 std::stringstream
보다 빠름
✅ printf
는 속도는 빠르지만 버퍼 크기 지정이 필요하고 타입 안전성이 부족
✅ std::stringstream
은 가독성이 좋지만 성능이 느리고 메모리 사용량이 많음
✅ std::format
은 빠른 속도와 낮은 메모리 사용량을 동시에 만족
💡 결론:
C++20 이상을 사용한다면 std::format
이 최선의 선택입니다. 가독성이 뛰어나고, 속도와 메모리 사용량도 최적화되어 있기 때문입니다.
다음 절에서는 실전에서 사용할 수 있는 다양한 활용 예제를 살펴보겠습니다.
실전 활용 예제
C++20의 <format>
라이브러리는 다양한 실전 환경에서 안전하고 효율적인 문자열 포매팅을 제공합니다. 이번 절에서는 로그 출력, JSON 데이터 변환, 테이블 형식 출력, 파일 저장 등 실전에서 활용할 수 있는 다양한 예제를 소개합니다.
1. 로그 시스템 구현
로그 메시지를 출력할 때 std::format
을 사용하면 간결하면서도 가독성이 좋은 로그 시스템을 만들 수 있습니다.
✅ 기본 로그 메시지 포매팅
#include <iostream>
#include <format>
#include <chrono>
void log(const std::string& level, const std::string& message) {
auto now = std::chrono::system_clock::now();
auto time = std::chrono::system_clock::to_time_t(now);
std::cout << std::format("[{}] [{}] {}\n", std::put_time(std::localtime(&time), "%Y-%m-%d %H:%M:%S"), level, message);
}
int main() {
log("INFO", "Application started");
log("WARNING", "Low disk space");
log("ERROR", "Failed to open file");
}
출력 결과:
[2025-01-31 12:34:56] [INFO] Application started
[2025-01-31 12:34:56] [WARNING] Low disk space
[2025-01-31 12:34:56] [ERROR] Failed to open file
✅ {}
을 사용해 시간, 로그 레벨, 메시지를 직관적으로 포매팅
✅ std::put_time(std::localtime(&time), "%Y-%m-%d %H:%M:%S")
을 활용해 날짜 및 시간 출력
2. JSON 데이터 변환
데이터를 JSON 형식으로 변환할 때도 std::format
을 활용하면 간결하고 직관적인 코드 작성이 가능합니다.
✅ JSON 객체 포매팅
#include <iostream>
#include <format>
struct User {
std::string name;
int age;
std::string email;
};
std::string to_json(const User& user) {
return std::format(
"{{ \"name\": \"{}\", \"age\": {}, \"email\": \"{}\" }}",
user.name, user.age, user.email
);
}
int main() {
User user{"Alice", 28, "alice@example.com"};
std::cout << "JSON Output:\n" << to_json(user) << std::endl;
}
출력 결과:
JSON Output:
{ "name": "Alice", "age": 28, "email": "alice@example.com" }
✅ std::format
을 활용하여 JSON 구조를 직관적으로 포매팅
✅ 문자열 연결 없이 가독성이 뛰어난 JSON 생성 가능
3. 테이블 형식 데이터 출력
콘솔에서 테이블 형태로 정렬된 데이터를 출력할 때 std::format
을 활용하면 쉽게 구현할 수 있습니다.
✅ 열 맞추기 (왼쪽 정렬 & 오른쪽 정렬)
#include <iostream>
#include <format>
#include <vector>
struct Product {
std::string name;
double price;
int quantity;
};
void print_table(const std::vector<Product>& products) {
std::cout << std::format("{:<15} {:>10} {:>8}\n", "Product", "Price", "Qty");
std::cout << "------------------------------------\n";
for (const auto& product : products) {
std::cout << std::format("{:<15} {:>10.2f} {:>8}\n", product.name, product.price, product.quantity);
}
}
int main() {
std::vector<Product> products = {
{"Laptop", 999.99, 5},
{"Smartphone", 699.49, 12},
{"Headphones", 149.99, 30},
{"Monitor", 249.99, 7}
};
print_table(products);
}
출력 결과:
Product Price Qty
------------------------------------
Laptop 999.99 5
Smartphone 699.49 12
Headphones 149.99 30
Monitor 249.99 7
✅ {:<15}
→ 15칸을 확보하고 왼쪽 정렬
✅ {:>10.2f}
→ 10칸 확보, 오른쪽 정렬, 소수점 2자리 표시
✅ {:>8}
→ 8칸 확보, 오른쪽 정렬
4. 파일에 포맷된 데이터 저장
로그나 데이터 파일을 저장할 때 std::format
을 활용하면 파일 형식을 손쉽게 유지할 수 있습니다.
✅ CSV 형식 데이터 저장
#include <iostream>
#include <fstream>
#include <format>
#include <vector>
struct Employee {
std::string name;
int id;
double salary;
};
void save_to_csv(const std::vector<Employee>& employees, const std::string& filename) {
std::ofstream file(filename);
file << "Name,ID,Salary\n";
for (const auto& emp : employees) {
file << std::format("{},{},{:.2f}\n", emp.name, emp.id, emp.salary);
}
file.close();
}
int main() {
std::vector<Employee> employees = {
{"John Doe", 101, 50000.50},
{"Jane Smith", 102, 60000.75},
{"Alice Brown", 103, 55000.25}
};
save_to_csv(employees, "employees.csv");
std::cout << "CSV 파일 저장 완료.\n";
}
출력 (CSV 파일 내용):
Name,ID,Salary
John Doe,101,50000.50
Jane Smith,102,60000.75
Alice Brown,103,55000.25
✅ std::format("{},{},{:.2f}\n", emp.name, emp.id, emp.salary)
을 사용하여 CSV 형식 유지
✅ std::ofstream
을 활용해 데이터를 파일에 저장
5. 다국어 및 로캘 지원 활용
국가별 형식에 맞춰 데이터를 출력해야 하는 경우에도 std::format
과 std::locale
을 조합하면 쉽게 적용할 수 있습니다.
✅ 통화 형식 적용
#include <iostream>
#include <format>
#include <locale>
int main() {
double price = 1999.99;
std::cout.imbue(std::locale("en_US.UTF-8"));
std::cout << std::format(std::locale("en_US.UTF-8"), "US Price: ${:n}\n", price);
std::cout.imbue(std::locale("de_DE.UTF-8"));
std::cout << std::format(std::locale("de_DE.UTF-8"), "German Price: {:n} €\n", price);
}
출력 결과:
US Price: $1,999.99
German Price: 1.999,99 €
✅ std::locale
을 적용하여 국가별 통화 및 숫자 포맷을 자동 변환
요약
✅ 로그 시스템 → std::format
을 활용해 깔끔한 로그 출력 구현
✅ JSON 변환 → {}
를 활용하여 JSON 데이터 생성
✅ 테이블 형식 출력 → 정렬 및 패딩을 활용한 데이터 출력
✅ 파일 저장 → CSV 및 로그 데이터를 포맷하여 저장
✅ 다국어 지원 → std::locale
을 활용한 통화 및 숫자 형식 변환
다음 절에서는 기사의 핵심 내용을 요약하겠습니다.
요약
C++20에서 새롭게 도입된 <format>
라이브러리는 기존의 printf
및 std::stringstream
방식보다 더 안전하고 간결한 문자열 포매팅을 제공합니다. 이를 통해 가독성을 높이고, 타입 안정성을 유지하며, 성능까지 향상시킬 수 있습니다.
✅ 핵심 정리
- 기존 방식의 문제점:
printf
는 타입 불일치 문제와 버퍼 오버플로우 위험이 있고,std::stringstream
은 성능이 낮고 코드가 복잡함. - 기본 사용법:
{}
플레이스홀더를 사용해 직관적으로 문자열을 포매팅하며, 타입 안정성을 제공. - 서식 지정자 활용: 소수점 자리수, 정렬, 패딩, 진법 변환 등 다양한 서식 옵션 제공.
- 로캘 지원: 숫자, 통화, 날짜 포맷을 다국어 환경에 맞게 변환 가능.
- 사용자 정의 타입 포매팅:
std::formatter<T>
를 오버라이드하여 객체를 원하는 형식으로 출력 가능. - 성능 비교:
std::format
은std::stringstream
보다 빠르고,printf
보다 안전함. - 실전 활용 예제: 로그 시스템, JSON 변환, 테이블 출력, 파일 저장 등 다양한 실전 적용 사례 소개.
✅ 결론
C++20을 사용하는 경우, 문자열 포매팅에는 std::format
을 적극적으로 활용하는 것이 가장 바람직합니다. 가독성, 성능, 메모리 사용량, 안전성 측면에서 기존 방식보다 우수하며, 실전에서도 다양한 활용이 가능합니다.
이제 문자열 포매팅을 더 안전하고 효율적으로 관리하고 싶다면 std::format
을 선택하세요! 🚀