C++20 Modules 기초와 실전 활용
C++ 모듈은 40년 이상 사용된 #include 기반 헤더 시스템을 대체하는 새로운 코드 조직화 메커니즘입니다. 빌드 속도 향상, 매크로 격리, 명시적 인터페이스 정의 등 여러 장점을 제공합니다.
1. 헤더 파일의 문제점
섹션 제목: “1. 헤더 파일의 문제점”// 헤더 파일의 전통적 문제들#include <vector> // 전체 vector 구현 코드 복붙#include <string> // 다시 복붙// 컴파일 단위마다 반복, 빌드 시간 폭증
// 매크로 오염#define MAX_SIZE 100 // 전역으로 퍼짐모듈은 이를 해결합니다. 모듈 인터페이스는 한 번만 컴파일되고 바이너리 형태로 재사용됩니다.
2. 기본 문법
섹션 제목: “2. 기본 문법”2.1 모듈 인터페이스 단위 (.ixx 또는 .cppm)
섹션 제목: “2.1 모듈 인터페이스 단위 (.ixx 또는 .cppm)”export module math; // 모듈 선언
import <string>; // 표준 라이브러리 임포트 (모듈화된 경우)
// export: 외부에서 사용 가능export int add(int a, int b) { return a + b;}
export double pi = 3.14159265358979;
// export 없음: 모듈 내부에서만 사용static int helper(int x) { return x * 2;}2.2 모듈 사용
섹션 제목: “2.2 모듈 사용”import math; // 모듈 임포트#include <iostream> // 헤더와 혼용 가능
int main() { std::cout << add(3, 4) << "\n"; // 7 std::cout << pi << "\n"; // 3.14159... // helper(5); // 오류: export 되지 않음 return 0;}3. Export 상세
섹션 제목: “3. Export 상세”3.1 export 블록
섹션 제목: “3.1 export 블록”export module geometry;
export { struct Point { double x, y; }; struct Rect { Point tl, br; }; double area(const Rect& r);}
// 구현 (export 불필요)double area(const Rect& r) { return (r.br.x - r.tl.x) * (r.br.y - r.tl.y);}3.2 export import (재내보내기)
섹션 제목: “3.2 export import (재내보내기)”export module mylib;
export import math; // math 모듈의 export를 재내보냄export import geometry; // geometry도 재내보냄4. 모듈 파티션
섹션 제목: “4. 모듈 파티션”대형 모듈을 논리적 파티션으로 분할할 수 있습니다.
// engine-render.ixx (파티션)export module engine:render; // engine 모듈의 render 파티션
export void renderFrame();export void setResolution(int w, int h);
void renderFrame() { /* 구현 */ }void setResolution(int w, int h) { /* 구현 */ }export module engine:physics;
export void updatePhysics(float dt);
void updatePhysics(float dt) { /* 구현 */ }// engine.ixx (기본 모듈 인터페이스)export module engine;
export import :render; // 파티션 재내보내기export import :physics;import engine; // render, physics 모두 접근 가능
int main() { renderFrame(); updatePhysics(0.016f); return 0;}5. 구현 단위 (Module Implementation Unit)
섹션 제목: “5. 구현 단위 (Module Implementation Unit)”인터페이스와 구현을 분리할 수 있습니다.
// logger.ixx (인터페이스)export module logger;
export class Logger {public: void log(const char* msg); void warn(const char* msg); void error(const char* msg);};// logger.cpp (구현 단위)module logger; // export 없음 = 구현 단위
#include <cstdio>
void Logger::log(const char* msg) { std::printf("[LOG] %s\n", msg);}
void Logger::warn(const char* msg) { std::printf("[WARN] %s\n", msg);}
void Logger::error(const char* msg) { std::printf("[ERROR] %s\n", msg);}6. 헤더 단위 (Header Units)
섹션 제목: “6. 헤더 단위 (Header Units)”기존 헤더 파일을 모듈처럼 임포트할 수 있습니다.
// 헤더 단위로 임포트 (컴파일러 지원 필요)import <vector>;import <string>;import "my_legacy_header.h";이는 완전한 모듈은 아니지만 빌드 속도를 개선하는 중간 단계로 유용합니다.
7. 빌드 시스템 설정
섹션 제목: “7. 빌드 시스템 설정”CMake 3.28+
섹션 제목: “CMake 3.28+”cmake_minimum_required(VERSION 3.28)project(MyProject)
set(CMAKE_CXX_STANDARD 20)set(CMAKE_CXX_SCAN_FOR_MODULES ON)
add_executable(app main.cpp math.ixx geometry.ixx)MSVC (Visual Studio)
섹션 제목: “MSVC (Visual Studio)”cl /std:c++20 /experimental:module /interface math.ixxcl /std:c++20 /experimental:module main.cpp math.ixx.obj8. 점진적 마이그레이션 전략
섹션 제목: “8. 점진적 마이그레이션 전략”기존 코드베이스를 한 번에 모듈로 전환하는 것은 현실적이지 않습니다.
// 전략 1: 헤더 파일 래핑export module legacy;
// 기존 헤더를 global module fragment에 포함module; // global module fragment 시작#include "old_library.h" // 매크로, 전처리기 내용 포함 가능
export module legacy; // named module 시작
// old_library.h의 타입을 재내보내거나 래핑export using OldType = ::OldLibraryType;9. 헤더 vs 모듈 비교
섹션 제목: “9. 헤더 vs 모듈 비교”| 항목 | 헤더 파일 | 모듈 |
|---|---|---|
| 빌드 속도 | 파일마다 재파싱 | 한 번만 컴파일 |
| 매크로 격리 | 전역으로 누출 | 모듈 내부 격리 |
| 순환 의존성 | 문제 발생 | 명시적 오류 |
| include guard | 필요 | 불필요 |
| 도입 비용 | 없음 | 빌드 시스템 지원 필요 |
10. Global Module Fragment — 레거시 헤더 통합
섹션 제목: “10. Global Module Fragment — 레거시 헤더 통합”모듈 내부에서 매크로를 포함한 기존 헤더를 사용해야 할 때 global module fragment를 활용합니다.
module; // global module fragment 시작 — 이름 없는 영역
// 레거시 헤더는 여기에만 포함 (매크로, 전처리기 지시문 포함 가능)#include <cstdio>#include <windows.h> // 플랫폼 헤더#define LEGACY_MACRO 1 // 이 매크로는 모듈 외부로 누출되지 않음
export module mylib; // named module 시작
// global fragment에서 포함한 헤더의 심볼 사용 가능export void log(const char* msg){ std::printf("[LOG] %s\n", msg);}11. 컴파일러별 모듈 빌드 방법
섹션 제목: “11. 컴파일러별 모듈 빌드 방법”GCC 14+
섹션 제목: “GCC 14+”# 모듈 인터페이스 컴파일g++ -std=c++20 -fmodules-ts -x c++-module math.ixx -o math.gcm
# 모듈을 사용하는 파일 컴파일g++ -std=c++20 -fmodules-ts main.cpp -o mainClang 16+
섹션 제목: “Clang 16+”# 모듈 인터페이스 유닛 컴파일clang++ -std=c++20 --precompile math.ixx -o math.pcm
# 모듈을 사용하는 파일 컴파일clang++ -std=c++20 -fmodule-file=math.pcm main.cpp -o mainMSVC (Visual Studio 2022+)
섹션 제목: “MSVC (Visual Studio 2022+)”:: 모듈 인터페이스 컴파일cl /std:c++20 /EHsc /interface /TP math.ixx
:: 모듈 사용 파일 컴파일cl /std:c++20 /EHsc main.cpp math.objCMake 3.28+ (권장)
섹션 제목: “CMake 3.28+ (권장)”cmake_minimum_required(VERSION 3.28)project(ModuleDemo CXX)
set(CMAKE_CXX_STANDARD 20)set(CMAKE_CXX_SCAN_FOR_MODULES ON) # 자동 모듈 의존성 스캔
add_executable(app main.cpp src/math.ixx src/string_utils.ixx)
# 컴파일러가 모듈 의존성을 자동으로 처리target_compile_features(app PRIVATE cxx_std_20)12. 실전 모듈 구조 예시
섹션 제목: “12. 실전 모듈 구조 예시”project/├── CMakeLists.txt├── src/│ ├── main.cpp│ ├── core/│ │ ├── core.ixx # export module core;│ │ ├── core-types.ixx # export module core:types;│ │ └── core-utils.ixx # export module core:utils;│ └── io/│ ├── io.ixx # export module io;│ └── io.cpp # module io; (구현 단위)export module core:types;
export struct Vector3 { float x, y, z; Vector3 operator+(const Vector3& o) const { return {x + o.x, y + o.y, z + o.z}; }};
export using Point3D = Vector3;// core/core.ixx — 파티션 집계export module core;
export import :types; // core:types 재내보내기export import :utils; // core:utils 재내보내기import core; // types, utils 모두 접근 가능
int main(){ Vector3 a{1, 2, 3}; Vector3 b{4, 5, 6}; auto c = a + b; // {5, 7, 9}}13. 자주 하는 실수와 함정
섹션 제목: “13. 자주 하는 실수와 함정”// 함정 1: export와 #define은 함께 사용 불가export module mylib;// #define MY_MACRO 1 // 이 매크로는 import하는 쪽에 전달되지 않음// export #define ... // 문법 오류 — 매크로는 export 불가
// 함정 2: 모듈에서 전처리기 지시문은 global fragment에만 허용module;#include <cmath> // OK — global fragmentexport module mylib;// #include <algorithm> // 경고 또는 오류 — named module 영역에서는 지양
// 함정 3: 같은 TU에서 헤더와 모듈 import 순서import std.core; // 모듈 import는 먼저#include <cstdio> // 헤더 include는 나중 (또는 global fragment에서)
// 함정 4: inline 함수는 모듈에서 정의가 필요export module utils;export inline int square(int x) { return x * x; } // OK — 인라인은 정의 포함// export int cube(int x); // 선언만 export하면 링크 오류 가능 (구현 단위 필요)C++20 모듈은 C++ 빌드 시스템의 근본적인 개선을 가져옵니다. 컴파일 시간 단축, 명확한 인터페이스 정의, 매크로 오염 방지라는 세 가지 핵심 이점을 제공합니다. 2025년 기준으로 MSVC, GCC 14+, Clang 16+ 모두 기본적인 모듈 지원을 제공하므로 새 프로젝트부터 점진적으로 도입해볼 것을 권장합니다.
도입 로드맵:
- 새 유틸리티 모듈부터
.ixx/.cppm으로 작성 - CMake 3.28+로 자동 의존성 스캔 활성화
- 기존 헤더는 global fragment 래핑으로 통합
- 점진적으로 모듈 파티션 구조로 리팩터링