Modern C++
C++20, C++23, C++26 관용구를 사용한 현대 C++ 작성을 위한 가이드입니다. Trail of Bits의 보안 강조와 함께 취약점 클래스를 제거하고 상용구를 줄이는 패턴에 중점을 둡니다.
이 스킬을 사용해야 하는 경우
- 새 C++ 함수, 클래스 또는 라이브러리 작성 시
- 기존 C++ 코드 현대화(Pre-C++20 패턴)
- 레거시와 현대적 접근 방식 간 선택이 필요할 때
- 보안 중요 또는 안전 중요 C++ 작업 시
- C++ 코드 리뷰에서 현대 관용구 채택 여부 확인 시
이 스킬을 사용하지 말아야 하는 경우
- 사용자가 명시적으로 이전 표준을 요구하는 경우: 제약 조건(임베디드, 레거시 ABI)을 존중하세요.
- 순수 C 코드: 이 스킬은 C++ 전용입니다.
- 빌드 시스템 질문: CMake, Meson, Bazel 구성은 범위를 벗어납니다.
- C++ 이외의 프로젝트: C++이 주가 아닌 혼합 코드베이스.
피해야 할 안티 패턴
| 피할 것 | 대신 사용할 것 | 이유 |
|---|---|---|
new/delete | std::make_unique, std::make_shared | 누수, 이중 해제 제거 |
| 원시 소유 포인터 | std::unique_ptr, std::shared_ptr | RAII 소유권 의미론 |
C 배열 (int arr[N]) | std::array<int, N> | 경계 인식, 값 의미론 |
| 포인터 + 길이 매개변수 | std::span<T> | 비소유, 경계 검사 가능 |
printf / sprintf | std::format, std::print | 타입 안전, 버퍼 오버플로우 없음 |
C 스타일 캐스트 (int)x | static_cast<int>(x) | 명시적 의도, 감사 가능 |
#define 상수 | constexpr 변수 | 범위 지정, 타입 지정, 디버깅 가능 |
SFINAE / enable_if | Concepts + requires | 읽기 쉬운 제약 조건과 오류 |
| 오류 코드 + 출력 매개변수 | std::expected<T, E> | 구성 가능, 타입 안전 오류 |
union | std::variant | 타입 안전, 무음 UB 없음 |
원시 mutex.lock()/unlock() | std::scoped_lock | 예외 안전, 교착 상태 없음 |
std::thread | std::jthread | 자동 조인, 중지 토큰 지원 |
assert() 매크로 | contract_assert (C++26) | 도구에 표시, 구성 가능 |
| 수동 CRTP | Deducing this (C++23) | 더 간단, 템플릿 상용구 없음 |
| 매크로 코드 생성 | Reflection (C++26) | 제로 오버헤드, 구성 가능 |
전체 표(30개 이상 패턴)는 anti-patterns.md를 참조하세요.
결정 트리
무엇을 하고 있나요?
|
+-- 새 C++ 코드를 작성 중인가요?
| +-- 기본적으로 현대 관용구 사용 (C++20/23)
| +-- 컴파일러가 지원하는 최신 표준 선택
| +-- 아래 기능 등급(Feature Tiers) 참조
|
+-- 기존 코드를 현대화 중인가요?
| +-- Tier 1 (C++20/23) 대체부터 시작
| +-- 보안 영향 순으로 우선순위 지정 (메모리 > 타입 > 스타일)
| +-- 마이그레이션 표는 anti-patterns.md 참조
|
+-- 보안 중요 코드인가요?
| +-- 컴파일러 강화 플래그 활성화 (아래 참조)
| +-- 강화된 libc++ 모드 활성화
| +-- CI에서 샌드타이저 실행
| +-- safe-idioms.md 및 compiler-hardening.md 참조
|
+-- C++26 기능을 사용 중인가요?
+-- Reflection: 예, 계획하세요 (GCC 16+)
+-- Contracts: 새로운 API 경계에 대해 신중히 사용
+-- std::execution: 생태계 성숙도 대기
+-- cpp26-features.md 참조
기능 등급
기능은 표준 버전이 아닌 오늘날의 실용적 사용 가능성에 따라 순위가 매겨집니다.
Tier 1: 오늘 사용 가능 (C++20/23, 견고한 컴파일러 지원)
| 기능 | 대체 대상 | 표준 |
|---|---|---|
Concepts + requires | SFINAE, enable_if | C++20 |
| Ranges + views | 원시 반복자 루프 | C++20 |
std::span<T> | 포인터 + 길이 | C++20 |
std::format | sprintf, iostream 체인 | C++20 |
삼자 비교 <=> | 수동 비교 연산자 | C++20 |
std::jthread | std::thread + 수동 조인 | C++20 |
| 지정 초기화자 | 위치 기반 구조체 초기화 | C++20 |
std::expected<T,E> | 오류 코드, 경계에서의 예외 | C++23 |
std::print / std::println | printf, std::cout << | C++23 |
Deducing this | CRTP, const/non-const 중복 | C++23 |
std::flat_map | 읽기 중심 사용을 위한 std::map | C++23 |
모나딕 std::optional | optional에 대한 중첩 if 검사 | C++23 |
cpp20-features.md 및 cpp23-features.md를 참조하세요.
Tier 2: 지금 배포 가능 (표준 버전 변경 불필요)
이 기능들은 C++ 표준 버전을 변경하지 않고도 안전성을 향상시킵니다:
- 컴파일러 강화 플래그 —
-D_FORTIFY_SOURCE=3,-fstack-protector-strong,-ftrivial-auto-var-init=zero - 강화된 libc++ — 약 0.3% 오버헤드의 경계 검사를 위해
-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST사용 - CI의 샌드타이저 — 최소 ASan + UBSan; 동시 코드의 경우 TSan
- 경고 플래그 —
-Wall -Wextra -Wpedantic -Werror
compiler-hardening.md를 참조하세요.
Tier 3: 계획 필요 (C++26, 주변 재구성 가치 있음)
Reflection은 가장 혁신적인 C++26 기능입니다. 다음을 제거합니다:
- 직렬화 상용구 (일반 함수 하나가 구조체별
to_json을 대체) - 코드 생성기 (protobuf 코드 생성, Qt MOC)
- 매크로 기반 등록 및 enum-to-string 해킹
GCC 16 (2026년 4월)에 reflection이 병합되었습니다. 혜택을 받을 수 있도록 새 코드를 계획하세요.
Tier 4: 관찰 필요 (C++26, 성숙도 필요)
- Contracts (
pre/post/contract_assert) —assert()보다 낫지만, 가상 함수 지원이 없고 컴파일러 지원이 제한적입니다. 새로운 API 경계에 대해 신중히 채택하세요. - std::execution (senders/receivers) — 강력한 비동기 프레임워크이지만, 가파른 학습 곡선, 스케줄러 미포함, 문서화 부족 등이 있습니다. 생태계 성숙도를 기다리세요.
cpp26-features.md를 참조하세요.
컴파일러 강화 빠른 참조
필수 플래그 (GCC + Clang)
-Wall -Wextra -Wpedantic -Werror
-D_FORTIFY_SOURCE=3
-fstack-protector-strong
-fstack-clash-protection
-ftrivial-auto-var-init=zero
-fPIE -pie
-Wl,-z,relro,-z,now
Clang 전용
-Wunsafe-buffer-usage
강화된 libc++ (Clang/libc++ 전용)
-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST
Google은 이를 Chrome 및 서버 팜 전체에 배포하여 약 0.3% 오버헤드, 1000개 이상의 버그 발견, 프로덕션 세그폴트 30% 감소를 달성했습니다.
전체 가이드는 compiler-hardening.md를 참조하세요.
거부해야 할 합리화
| 합리화 | 잘못된 이유 |
|---|---|
| "경고 없이 컴파일됩니다" | 경고는 활성화한 플래그에 따라 다릅니다. -Wall -Wextra -Wpedantic을 추가하세요. |
| "ASan은 프로덕션에 너무 느립니다" | 샘플링 기반 프로덕션 탐지를 위해 GWP-ASan을 사용하세요 (약 0% 오버헤드). |
| "안전한 컨테이너만 사용합니다" | 반복자 무효화 및 확인되지 않은 optional 접근은 여전히 악용 가능합니다. |
| "스마트 포인터는 더 느립니다" | std::unique_ptr은 원시 포인터 대비 제로 오버헤드입니다. 주장하기 전에 측정하세요. |
| "우리 코드에는 메모리 버그가 없습니다" | Google은 강화된 libc++를 활성화했을 때 1000개 이상의 버그를 발견했습니다. 다른 곳도 마찬가지입니다. |
| "C++26 기능을 아직 사용할 수 없습니다" | C++20/23 기능은 사용 가능합니다. 강화 플래그는 모든 표준에서 작동합니다. 거기서 시작하세요. |
| "현대 C++은 읽기 더 어렵습니다" | std::expected는 5개의 출력 매개변수에서 오류 코드를 확인하는 것보다 읽기 쉽습니다. |
모범 사례 체크리스트
- 소유권에는 스마트 포인터를 사용하고, 비소유 관찰에는 원시 포인터만 사용하세요.
- 함수 매개변수에는 포인터 + 길이보다
std::span을 선호하세요. - 타입화된 오류로 실패할 수 있는 함수에는
std::expected를 사용하세요. - 템플릿을 SFINAE 대신 개념으로 제약하세요.
- 모든 빌드에서 컴파일러 강화 플래그와 강화된 libc++를 활성화하세요.
- CI에서 ASan + UBSan을 실행하고, 동시 코드에는 TSan을 추가하세요.
- 가능한 경우
constexpr/consteval을 사용하세요 (설계상 UB 없음). - 반환 값을 무시하는 것이 버그일 가능성이 있는 함수에
[[nodiscard]]를 표시하세요. - 값 의미론을 선호하고,
union대신std::variant,enum대신enum class를 사용하세요. - 모든 변수를 선언 시 초기화하세요.
다음 읽을거리
- anti-patterns.md — 전체 레거시-현대 마이그레이션 표 (30개 이상 패턴)
- cpp20-features.md — Concepts, ranges, span, format, coroutines
- cpp23-features.md — expected, print, deducing this, flat_map
- cpp26-features.md — Reflection, contracts, memory safety improvements
- compiler-hardening.md — Flags, sanitizers, hardened libc++
- safe-idioms.md — Security patterns by vulnerability class