본문으로 건너뛰기 C++ 컴파일 타임 최적화 | constexpr·PCH·모듈·ccache·Unity 빌드 [#15-3]

C++ 컴파일 타임 최적화 | constexpr·PCH·모듈·ccache·Unity 빌드 [#15-3]

C++ 컴파일 타임 최적화 | constexpr·PCH·모듈·ccache·Unity 빌드 [#15-3]

💡 초보자를 위한 한 줄: 항상 같은 상수·테이블이면 constexpr컴파일 타임에 박아 두며, 타입마다 분기가 필요하면 if constexpr. 빌드가 느리면 PCH·ccache·모듈부터 손대는 편이 ROI가 좋습니다. 15-2 캐시 친화 코드와 세트로 보면 좋습니다.

들어가며: 매번 같은 계산을 반복한다

”프로그램 시작할 때마다 똑같은 테이블을 계산해요”

룩업 테이블(미리 계산해 둔 값 배열—인덱스로 바로 결과를 찾을 때 사용)을 런타임(실행 중)에 초기화하고 있었습니다. 하지만 값은 항상 같았습니다.
constexpr과 if constexpr을 쓰면 “항상 같은 값”이나 “타입에 따라 다른 분기”를 컴파일 타임에 처리해서, 런타임 비용을 없애고 코드 경로를 단순하게 유지할 수 있습니다. 다만 컴파일 시간이 늘어나므로, 큰 메타프로그래밍은 모듈화해서 필요한 부분만 켜 두는 것이 실무에서 유리합니다.

추가 문제 시나리오

빌드가 10분 이상 걸려요

헤더 하나 수정하면 전체 리빌드가 돼서, 작은 수정에도 긴 대기 시간이 발생합니다. PCH·모듈·ccache로 해결할 수 있습니다. 템플릿 헤더가 너무 커서 컴파일이 느려요

<iostream>, <vector> 등이 거의 모든 TU에 포함되어 매번 파싱됩니다. PCH로 한 번만 컴파일해 재사용하면 됩니다. CI에서 매번 클린 빌드

PR마다 15분 이상 빌드, 캐시 없음. ccache·병렬 빌드로 빌드 시간을 크게 줄일 수 있습니다. switch에서 문자열 비교

런타임에 strcmp 반복 호출 대신, constexpr 해시로 컴파일 타임에 상수로 변환해 O(1) 비교가 가능합니다. 타입에 따라 다른 직렬화

POD 타입은 바이너리 복사, 복잡한 타입은 직렬화 함수 호출. if constexpr로 컴파일 타임에 분기하면 됩니다. 문제의 코드:

std::array<int, 256> lookupTable;
void initTable() {
    for (int i = 0; i < 256; ++i) {
        lookupTable[i] = i * i;  // 매번 계산
    }
}
int main() {
    initTable();  // 프로그램 시작마다 실행
    // ...
}

constexpr로 해결:

constexpr std::array<int, 256> makeLookupTable() {
    std::array<int, 256> table{};
    for (int i = 0; i < 256; ++i) {
        table[i] = i * i;  // ✅ 컴파일 타임에 계산
    }
    return table;
}
constexpr auto lookupTable = makeLookupTable();
int main() {
    // 테이블이 이미 준비됨 (초기화 비용 0)
}

주의사항: 컴파일 타임 계산 한도(constexpr 단계 제한)에 걸리면 빌드가 실패하므로, 거대한 메타프로그래밍은 단계적으로 나눕니다. 이 글을 읽으면

  • constexpr로 컴파일 타임 계산을 할 수 있습니다.
  • if constexpr로 조건 분기를 최적화할 수 있습니다.
  • 템플릿 메타프로그래밍의 기초를 이해할 수 있습니다.
  • PCH·모듈·ccache·Unity 빌드로 빌드 시간을 단축할 수 있습니다.
  • 자주 발생하는 에러와 프로덕션 패턴을 알 수 있습니다.

1. constexpr 기초

constexpr 변수

constexpr int x = 10;  // 컴파일 타임 상수
constexpr int y = x * 2;  // 20
// 배열 크기로 사용 가능
int arr[x];  // ✅ OK
// 일반 const는 불가능할 수 있음
const int z = getInput();  // 런타임 값
// int arr2[z];  // ❌ 에러 (컴파일 타임 상수 아님)

const vs constexpr

const int a = 10;        // 컴파일 타임 또는 런타임
constexpr int b = 10;    // 반드시 컴파일 타임
const int c = getInput();      // ✅ OK (런타임)
// constexpr int d = getInput();  // ❌ 에러

2. constexpr 함수

기본 사용법

constexpr int square(int x) {
    return x * x;
}
int main() {
    // 컴파일 타임 계산
    constexpr int a = square(5);  // 25 (컴파일 타임)
    
    // 런타임 계산도 가능
    int input;
    std::cin >> input;
    int b = square(input);  // 런타임
}

재귀 함수

// 실행 예제
constexpr int factorial(int n) {
    return n <= 1 ? 1 : n * factorial(n - 1);
}
int main() {
    constexpr int f5 = factorial(5);  // 120 (컴파일 타임)
    
    // 배열 크기로 사용
    int arr[factorial(4)];  // int arr[24]
}

복잡한 계산

constexpr int fibonacci(int n) {
    if (n <= 1) return n;
    
    int a = 0, b = 1;
    for (int i = 2; i <= n; ++i) {
        int temp = a + b;
        a = b;
        b = temp;
    }
    return b;
}
constexpr int fib10 = fibonacci(10);  // 55 (컴파일 타임)

constexpr 클래스

class Point {
    int x, y;
    
public:
    constexpr Point(int x, int y) : x(x), y(y) {}
    
    constexpr int getX() const { return x; }
    constexpr int getY() const { return y; }
    
    constexpr int distanceSquared() const {
        return x * x + y * y;
    }
};
int main() {
    constexpr Point p(3, 4);
    constexpr int dist = p.distanceSquared();  // 25 (컴파일 타임)
}

3. if constexpr (C++17)

컴파일 타임 분기

template <typename T>
void process(T value) {
    if constexpr (std::is_integral_v<T>) {
        std::cout << "Integer: " << value << "\n";
    } else if constexpr (std::is_floating_point_v<T>) {
        std::cout << "Float: " << value << "\n";
    } else {
        std::cout << "Other: " << value << "\n";
    }
}
int main() {
    process(42);      // Integer: 42
    process(3.14);    // Float: 3.14
    process("hello"); // Other: hello
}

일반 if vs if constexpr

template <typename T>
void func(T value) {
    // 일반 if: 두 분기 모두 컴파일됨
    if (std::is_integral_v<T>) {
        value++;  // T가 string이면 컴파일 에러
    }
    
    // if constexpr: 선택된 분기만 컴파일
    if constexpr (std::is_integral_v<T>) {
        value++;  // T가 string이면 이 코드 무시됨
    }
}

재귀 종료

template <typename T>
void print(T value) {
    std::cout << value << "\n";
}
template <typename T, typename... Args>
void print(T first, Args... rest) {
    std::cout << first << " ";
    
    if constexpr (sizeof...(rest) > 0) {
        print(rest...);  // 재귀
    } else {
        std::cout << "\n";
    }
}
int main() {
    print(1, 2, 3, "hello", 4.5);
}

4. consteval (C++20)

반드시 컴파일 타임

consteval int square(int x) {
    return x * x;
}
int main() {
    constexpr int a = square(5);  // ✅ OK (컴파일 타임)
    
    int input = 10;
    // int b = square(input);  // ❌ 에러: 런타임 호출 불가
}

constexpr vs consteval

constexpr int func1(int x) {
    return x * 2;  // 컴파일 타임 또는 런타임
}
consteval int func2(int x) {
    return x * 2;  // 반드시 컴파일 타임
}
int main() {
    constexpr int a = func1(5);  // ✅ 컴파일 타임
    int input = 10;
    int b = func1(input);        // ✅ 런타임
    
    constexpr int c = func2(5);  // ✅ 컴파일 타임
    // int d = func2(input);     // ❌ 에러
}

5. 실전 활용

패턴 1: 컴파일 타임 해시

constexpr uint32_t hash(const char* str) {
    uint32_t hash = 5381;
    while (*str) {
        hash = ((hash << 5) + hash) + *str++;
    }
    return hash;
}
int main() {
    // switch에서 문자열 비교
    constexpr uint32_t cmdOpen = hash("open");
    constexpr uint32_t cmdClose = hash("close");
    
    uint32_t cmd = hash(getUserInput());
    
    switch (cmd) {
        case cmdOpen:
            openFile();
            break;
        case cmdClose:
            closeFile();
            break;
    }
}

패턴 2: 컴파일 타임 배열 생성

template <size_t N>
constexpr std::array<int, N> makeSquares() {
    std::array<int, N> arr{};
    for (size_t i = 0; i < N; ++i) {
        arr[i] = i * i;
    }
    return arr;
}
constexpr auto squares = makeSquares<100>();
int main() {
    std::cout << squares[10] << "\n";  // 100 (이미 계산됨)
}

패턴 3: 타입 특성 확인

template <typename T>
constexpr bool isTrivial() {
    return std::is_trivially_copyable_v<T> &&
           std::is_trivially_destructible_v<T>;
}
template <typename T>
void serialize(const T& value) {
    if constexpr (isTrivial<T>()) {
        // POD 타입: 바이너리 복사
        write(&value, sizeof(T));
    } else {
        // 복잡한 타입: 직렬화 함수 호출
        value.serialize();
    }
}

패턴 4: 컴파일 타임 문자열 처리

constexpr size_t stringLength(const char* str) {
    size_t len = 0;
    while (str[len] != '\0') {
        ++len;
    }
    return len;
}
constexpr bool startsWith(const char* str, const char* prefix) {
    while (*prefix) {
        if (*str++ != *prefix++) {
            return false;
        }
    }
    return true;
}
int main() {
    constexpr size_t len = stringLength("Hello");  // 5
    constexpr bool result = startsWith("Hello", "Hel");  // true
}

패턴 5: 컴파일 타임 팩토리얼 테이블

template <size_t N>
constexpr std::array<long long, N> makeFactorials() {
    std::array<long long, N> arr{};
    arr[0] = 1;
    for (size_t i = 1; i < N; ++i) {
        arr[i] = arr[i - 1] * i;
    }
    return arr;
}
constexpr auto factorials = makeFactorials<20>();
int main() {
    std::cout << "10! = " << factorials[10] << "\n";  // 즉시 출력
}

6. PCH(미리 컴파일된 헤더)

PCH란?

자주 쓰는 헤더들을 한 번만 컴파일해 .gch(GCC) 또는 .pch(Clang/MSVC)로 저장하며, 각 TU에서 이 바이너리를 재사용합니다. 헤더 파싱 시간을 30~50% 단축할 수 있습니다.

flowchart LR
    subgraph Before[PCH 없음]
        A1[main.cpp] --> A2[iostream 파싱]
        B1[foo.cpp] --> B2[iostream 파싱]
        C1[bar.cpp] --> C2[iostream 파싱]
    end
    subgraph After[PCH 사용]
        D1[pch.h.gch] --> D2[한 번만 생성]
        E1[main.cpp] --> E2[PCH 로드]
        F1[foo.cpp] --> F2[PCH 로드]
        G1[bar.cpp] --> G2[PCH 로드]
    end

PCH 헤더 작성

// pch.h - 미리 컴파일할 헤더
#ifndef PCH_H
#define PCH_H
// 자주 쓰는 표준 라이브러리 (변경 거의 없음)
#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include <algorithm>
#include <chrono>
// 프로젝트 공통 헤더
#include "config.h"
#include "types.h"
#endif

GCC에서 PCH 생성 및 사용

# 1. PCH 생성
g++ -x c++-header -std=c++20 -O2 -o pch.h.gch pch.h
# 2. 소스 컴파일 시 PCH 사용
g++ -std=c++20 -O2 -include pch.h -c main.cpp -o main.o

CMake에서 PCH 자동화

# CMakeLists.txt - PCH 지원
cmake_minimum_required(VERSION 3.16)
project(MyApp LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
# PCH 타겟
add_library(pch_header INTERFACE)
target_precompile_headers(pch_header INTERFACE
    <iostream>
    <string>
    <vector>
    <memory>
    "config.h"
)
add_executable(app main.cpp foo.cpp bar.cpp)
target_link_libraries(app PRIVATE pch_header)
target_precompile_headers(app REUSE_FROM pch_header)

주의: PCH 생성 시 -O2, -DDEBUG 등이 TU와 완전히 동일해야 합니다. PCH 헤더는 파일 최상단에 포함해야 합니다.

7. C++20 모듈

모듈 vs 헤더

항목#include 헤더C++20 모듈
파싱 횟수TU마다 반복한 번만 (BMI 캐시)
전이적 노출전체 노출export된 것만
템플릿 인스턴스TU마다 반복한 번만
빌드 순서순서 무관모듈 의존성 순서 필요

모듈 기본 예제

math.mpp (모듈 구현):

// math.mpp - C++20 모듈
module;
#include <cmath>  // 모듈 선언 전에만 전통적 #include
export module math;
export double sqrt_safe(double x) {
    return x >= 0 ? std::sqrt(x) : 0.0;
}
export template<typename T>
T clamp(T v, T lo, T hi) {
    return v < lo ? lo : (v > hi ? hi : v);
}

main.cpp (모듈 사용):

import math;  // #include 대신 import
#include <iostream>
int main() {
    std::cout << sqrt_safe(2.0) << '\n';
    std::cout << clamp(42, 0, 100) << '\n';
    return 0;
}

CMake에서 모듈 빌드

# CMakeLists.txt - C++20 모듈 지원
cmake_minimum_required(VERSION 3.28)
project(MyApp LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 모듈 파일을 먼저 컴파일 (의존성 순서)
add_library(math MODULE math.mpp)
target_compile_features(math PUBLIC cxx_std_20)
add_executable(app main.cpp)
target_link_libraries(app PRIVATE math)

주의: GCC 14+, Clang 18+, MSVC 2022 17.5+에서 모듈 지원이 안정화됩니다.

8. ccache로 재빌드 가속

ccache 동작 원리

flowchart TB
    subgraph Input[입력]
        S[소스 + 컴파일 옵션]
    end
    subgraph CCache[ccache]
        H[해시 계산]
        L{캐시\n존재?}
        R[캐시에서 복원]
        C[실제 컴파일]
        S2[캐시 저장]
    end
    S --> H --> L
    L -->|Hit| R
    L -->|Miss| C --> S2
    R --> O[오브젝트 출력]
    C --> O

소스+옵션 해시로 캐시 조회 → Hit이면 컴파일 생략, Miss면 컴파일 후 저장.

ccache 설치 및 설정

# Ubuntu/Debian
sudo apt install ccache
# macOS
brew install ccache
# PATH에 ccache 우선 배치
export PATH="/usr/lib/ccache:$PATH"
# 또는
export CC="ccache gcc"
export CXX="ccache g++"

CMake + ccache

# CMakeLists.txt
find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
    set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
    set(CMAKE_C_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
endif()
# 또는 빌드 시
cmake -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -B build
cmake --build build -j8

ccache 통계 확인

ccache -s
# 출력 예:
# cache hit (direct)                 1234
# cache hit (preprocessed)             56
# cache miss                          89
# cache hit rate                      93.54 %

9. Unity 빌드

Unity 빌드란?

여러 .cpp를 하나로 합쳐 컴파일 횟수를 줄입니다. 헤더 중복 파싱을 줄여 40~60% 단축 가능합니다.

수동 Unity 빌드

// unity.cpp - 여러 cpp를 한 번에 컴파일
#include "file1.cpp"
#include "file2.cpp"
#include "file3.cpp"
#include "file4.cpp"
g++ -O2 unity.cpp -o myapp

CMake Unity 빌드

# CMakeLists.txt
set(CMAKE_UNITY_BUILD ON)
add_executable(app main.cpp foo.cpp bar.cpp baz.cpp)
# → 내부적으로 하나의 unity_*.cpp로 합쳐서 컴파일

주의: static 변수 이름 충돌, 매크로 전역 오염 등에 유의. 익명 네임스페이스 사용 권장.

// ✅ static 대신 익명 네임스페이스
namespace {
int counter = 0;
}

10. 자주 발생하는 에러와 해결법

문제 1: PCH “file not found” 또는 “invalid precompiled header”

원인: PCH 생성 옵션과 TU 컴파일 옵션이 다름.

# ❌ 잘못된 예: PCH는 -O0, TU는 -O2
g++ -x c++-header -O0 -o pch.h.gch pch.h
g++ -O2 -include pch.h -c main.cpp  # 에러!

해결:

# ✅ 옵션 일치
g++ -x c++-header -std=c++20 -O2 -DNDEBUG -o pch.h.gch pch.h
g++ -std=c++20 -O2 -DNDEBUG -include pch.h -c main.cpp

문제 2: PCH 헤더 앞에 다른 #include가 있음

원인: PCH는 반드시 첫 번째 포함되어야 함.

// ❌ 잘못된 예
#include "other.h"   // PCH보다 먼저!
#include "pch.h"
// ✅ 올바른 예
#include "pch.h"
#include "other.h"

문제 3: constexpr 함수에서 “non-constant expression” 에러

원인: constexpr 함수 내에서 허용되지 않는 연산 사용.

// ❌ 잘못된 예
constexpr int bad() {
    static int x = 0;  // static 금지
    return x++;
}
// ❌ 잘못된 예
constexpr int bad2() {
    throw std::runtime_error("error");  // 예외 금지
}

해결:

// ✅ C++14: 루프, 변수 선언 가능
constexpr int good(int n) {
    int result = 0;
    for (int i = 0; i < n; ++i) {
        result += i;
    }
    return result;
}

문제 4: ccache Hit이 안 됨

원인: __FILE__, __DATE__, __TIME__ 등이 소스에 포함되어 매번 다른 입력으로 인식.

// ❌ ccache 무효화
std::cout << "Built at " << __DATE__ << " " << __TIME__;

해결: 빌드 타임스탬프는 링크 단계에서 주입하거나, 별도 생성 파일로 분리.

// ✅ 버전 정보를 별도 .cpp에서
// version.cpp - 빌드 스크립트가 생성
const char* build_time = "2026-02-28 12:00:00";

문제 5: 모듈 빌드 순서 오류

원인: 모듈 B가 모듈 A를 import하는데, A가 아직 빌드되지 않음.

# ✅ 의존성 순서 보장
add_library(mod_a MODULE a.mpp)
add_library(mod_b MODULE b.mpp)
target_link_libraries(mod_b PRIVATE mod_a)  # mod_a 먼저 빌드

문제 6: Unity Build에서 ODR 위반

원인: 여러 cpp를 합치면서 static 변수나 익명 네임스페이스가 중복.

// ❌ foo.cpp
static int counter = 0;
// ❌ bar.cpp (Unity로 합쳐지면)
static int counter = 0;  // ODR 위반!

해결: static 대신 익명 네임스페이스, 또는 Unity Build 비활성화.

// ✅
namespace {
int counter = 0;
}

11. 성능 최적화 팁

팁 1: 헤더 정리

// ❌ 불필요한 포함
#include <iostream>  // cout만 쓰는데
#include <vector>   // 사용 안 함
// ✅ 필요한 것만
#include <ostream>  // std::ostream
#include "my_types.h"

forward declaration 활용:

// ✅ 헤더에서
class Bar;  // forward declaration
void use_bar(Bar& b);  // Bar 정의 불필요
// .cpp에서
#include "bar.h"
void use_bar(Bar& b) { /* ... */ }

팁 2: 템플릿 분리

// ❌ 헤더에 모든 구현
template<typename T>
void process(T x) {
    // 긴 구현...
}
// ✅ 선언만 헤더, 구현은 .tpp
// util.hpp
template<typename T> void process(T x);
// util.tpp
#include "util.hpp"
template<typename T>
void process(T x) { /* ... */ }

필요한 타입만 .cpp에서 명시적 인스턴스화:

// util_instances.cpp
#include "util.tpp"
template void process<int>(int);
template void process<double>(double);

팁 3: 병렬 빌드

# make: -j N (N = 병렬 작업 수)
make -j$(nproc)
# CMake
cmake --build build -- -j8

팁 4: 증분 빌드 최대화

  • 빌드 디렉토리 고정: build/ 한 곳에 유지해 ccache Hit 극대화
  • 경로 정규화: ccache hash_dir = false로 절대 경로 차이 무시
  • 타임스탬프 분리: 소스는 Git, 빌드 아티팩트는 별도 저장

팁 5: CI에서 ccache 활용

# GitHub Actions 예시
- uses: actions/cache@v4
  with:
    path: ~/.ccache
    key: ccache-${{ runner.os }}-${{ hashFiles('**/CMakeLists.txt', '**/*.cpp') }}
    restore-keys: ccache-${{ runner.os }}-
- run: |
    ccache -s  # 캐시 통계
    cmake -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -B build
    cmake --build build -j4

12. 프로덕션 패턴

패턴 1: 계층별 빌드 전략

flowchart TB
    subgraph Layer1["1층: 안정 라이브러리"]
        L1[표준 라이브러리]
        L2[서드파티]
    end
    subgraph Layer2["2층: 프로젝트 코어"]
        M1[모듈/PCH]
    end
    subgraph Layer3["3층: 애플리케이션"]
        A1[앱 코드]
    end
    L1 --> L2 --> M1 --> A1
  • 1층: PCH 또는 모듈로 한 번만 빌드
  • 2층: 변경 적은 코어를 모듈화
  • 3층: 자주 바뀌는 앱 코드는 ccache·병렬에 의존

패턴 2: 빌드 프로파일

# CMakePresets.cmake 또는 프로파일
set(CMAKE_BUILD_TYPE "Release")
set(USE_PCH ON)
set(USE_CCACHE ON)
set(CMAKE_CXX_COMPILER_LAUNCHER "ccache")

패턴 3: 빌드 시간 측정

# 시간 측정 (GNU time)
/usr/bin/time -v cmake --build build -j8 2>&1 | grep -E "Elapsed|Maximum resident"
# GCC: -ftime-report
g++ -c -ftime-report -O2 main.cpp 2>&1 | tail -20

성능 비교 표

설정클린 빌드증분 빌드 (1파일 수정)
기본 (-j1)180초45초
-j835초12초
-j8 + ccache35초 (첫)2초
-j8 + ccache + PCH28초2초

구현 체크리스트

  • -j$(nproc) 또는 CMAKE_BUILD_PARALLEL_LEVEL 설정
  • ccache 설치 및 CMAKE_CXX_COMPILER_LAUNCHER=ccache 적용
  • PCH에 자주 쓰는 표준·프로젝트 헤더만 포함 (변경 잦은 헤더 제외)
  • PCH 생성 옵션과 TU 컴파일 옵션 일치 확인
  • CI에서 ccache 캐시 디렉토리 유지 (actions/cache 등)
  • 모듈 도입 시 CMake 3.28+ 및 GCC 14+/Clang 18+ 확인
  • 빌드 시간 로깅으로 개선 효과 측정

제약사항

constexpr 제약 (C++11)

// ❌ C++11: 제약 많음
constexpr int func(int x) {
    // 한 줄 return만 가능
    return x * 2;
}

constexpr 확장 (C++14)

// ✅ C++14: 루프, 변수 선언 가능
constexpr int func(int x) {
    int result = 0;
    for (int i = 0; i < x; ++i) {
        result += i;
    }
    return result;
}

constexpr 확장 (C++20)

// ✅ C++20: 동적 할당, 가상 함수 가능
constexpr int func() {
    std::vector<int> vec = {1, 2, 3};
    return vec[0] + vec[1] + vec[2];
}

성능 비교

런타임 vs 컴파일 타임

// 런타임 계산
int runtimeFib(int n) {
    if (n <= 1) return n;
    return runtimeFib(n - 1) + runtimeFib(n - 2);
}
// 컴파일 타임 계산
constexpr int compileFib(int n) {
    if (n <= 1) return n;
    return compileFib(n - 1) + compileFib(n - 2);
}
int main() {
    // 런타임 계산 (느림)
    auto start = std::chrono::high_resolution_clock::now();
    int r = runtimeFib(30);
    auto end = std::chrono::high_resolution_clock::now();
    // 약 10ms
    
    // 컴파일 타임 계산 (즉시)
    constexpr int c = compileFib(30);  // 0ms (이미 계산됨)
    std::cout << c << "\n";
}

같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.


이 글에서 다루는 키워드 (관련 검색어)

C++ 컴파일 타임 최적화, constexpr, 템플릿 메타프로그래밍, PCH, ccache, C++20 모듈, Unity 빌드 등으로 검색하시면 이 글이 도움이 됩니다.

정리

항목설명
constexpr컴파일/런타임 모두 가능
consteval반드시 컴파일 타임
if constexpr컴파일 타임 분기
PCH헤더 한 번만 파싱, 30~50% 단축
모듈C++20, 의존성 격리
ccache컴파일 결과 캐시, 재빌드 가속
Unity 빌드여러 cpp 합쳐 컴파일, 40~60% 단축
핵심 원칙:
  1. 고정 값은 constexpr
  2. 룩업 테이블은 컴파일 타임
  3. 타입 분기는 if constexpr
  4. 성능 크리티컬은 consteval
  5. 빌드 시간은 PCH·ccache·병렬로 최적화
  6. 측정으로 효과 검증

초보자를 위한 체크리스트

  • constexpr 함수가 실제로 상수 평가되는지(컴파일 에러로 확인) 검증했는가?
  • if constexpr false 쪽이 ill-formed가 되지 않도록 분기했는가?
  • Unity 빌드 도입 시 ODR·정적 초기화 순서 등 팀 규칙을 정했는가?

💡 초보자 팁: 본문 1. constexpr 기초·10. 자주 발생하는 에러·6. PCH를 함께 보세요.

자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. constexpr로 컴파일 타임에 계산하며, if constexpr로 조건 분기하며, PCH·ccache·모듈로 빌드 시간을 줄이는 방법을 다룹니다. 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다. 한 줄 요약: constexpr·템플릿 메타프로그래밍으로 컴파일 타임에 계산해 런타임 부담을 줄이며, PCH·ccache·모듈로 빌드 시간을 단축할 수 있습니다. 다음으로 GDB/LLDB(#16-1)를 읽어보면 좋습니다. 이전 글: [C++ 실전 가이드 #15-2] 캐시 친화적 코드: 메모리 접근 패턴 최적화 다음 글: [C++ 실전 가이드 #16-1] GDB/LLDB 디버거 완벽 가이드: 버그를 찾는 가장 강력한 도구

관련 글