26 5월 2025
C++ 생태계는 수십 년 만에 가장 심오한 변화를 겪고 있으며, 코드가 구성되고 컴파일되는 방식을 근본적으로 재구상하고 있습니다. 수년 동안 개발자들은 #include
시스템의 한계, 즉 느린 컴파일 시간, 광범위한 매크로 오염, 적절한 캡슐화 부족으로 어려움을 겪어왔습니다. 전처리기 기반 접근 방식의 이러한 내재된 결함은 대규모 개발에서 C++의 잠재력을 제한했으며, 엔지니어들이 복잡한 해결 방법과 빌드 시스템을 채택하도록 강요했습니다.
C++ 20 모듈은 이러한 오랜 과제에 대한 포괄적인 해결책으로 등장했으며, 언어의 시작 이래 C++ 코드 구성에서 첫 번째 주요 패러다임 전환을 나타냅니다. 모듈은 텍스트 포함을 구조화된 이진 인터페이스로 대체함으로써 컴파일 속도, 코드 격리 및 인터페이스 명확성에서 혁신적인 개선을 제공합니다. 이는 단지 점진적인 개선이 아니라 C++ 프로그램 구성의 바로 그 아키텍처를 다루는 근본적인 변화입니다.
전통적인 헤더 파일 모델은 현대 C++ 개발에 네 가지 근본적인 제약을 부과합니다:
C++ 모듈은 이러한 과제에 직접적으로 맞섭니다. 모듈은 한 번 이진 표현으로 컴파일되며, 컴파일러는 이를 텍스트 기반 헤더 파일보다 훨씬 빠르게 가져와 처리할 수 있습니다. 이러한 코드 변환은 컴파일 중의 중복 작업을 크게 줄여줍니다. 모듈은 또한 가시성을 엄격하게 제어하여 명시적으로 표시된 것만 내보내므로 매크로 오염을 방지하고 더 강력한 캡슐화를 강제합니다. 명시적으로 내보내지지 않은 이름과 매크로는 모듈 내부에 비공개로 유지되어 코드 격리를 개선합니다.
기본 구문을 이해하는 것은 C++ 모듈을 사용한 효과적인 프로그래밍의 기초를 형성합니다. 모듈 정의는 일반적으로 모듈이 내보내는 것을 선언하는 인터페이스 단위와 정의를 제공하는 구현 단위를 포함합니다.
수학 모듈에 대한 간단한 C++ 모듈 예제를 고려해 봅시다:
1. 모듈 인터페이스 단위 (math.ixx)
// math.ixx - Primary module interface unit for 'math'
export module math;
// Exported function declarations
export int add(int a, int b);
export int multiply(int a, int b);
// Internal helper function, not exported
int subtract_internal(int a, int b);
여기서 export module math;
는 이 파일이 math
라는 이름의 모듈에 대한 기본 인터페이스임을 선언합니다. add
와 multiply
앞의 export
키워드는 이 함수들이 모듈의 공개 인터페이스의 일부이며, math
를 가져오는 다른 번역 단위에서 접근할 수 있음을 나타냅니다. export
가 없는 subtract_internal
함수는 모듈에 비공개로 유지됩니다.
2. 모듈 구현 단위 (math.cpp)
// math.cpp - Module implementation unit for 'math'
module math; // Associate this file with the 'math' module
// Definitions for exported functions
int add(int a, int b) {
return a + b;
}
int multiply(int a, int b) {
return a * b;
}
// Definition for internal helper function
int subtract_internal(int a, int b) {
return a - b;
}
이 파일은 module math;
로 시작하며, 이는 이 파일을 math
모듈과 연결합니다. math.ixx
에 선언된 함수들의 정의를 포함합니다. 구현 파일은 모듈 선언에 export
를 사용하지 않는다는 점에 유의하십시오.
3. 모듈 사용하기 (main.cpp)
// main.cpp - Consuming the 'math' module
import math; // Import the 'math' module
#include <iostream> // For console output
int main() {
int sum = add(10, 5); // Call exported function
int product = multiply(10, 5); // Call exported function
std::cout << "Sum: " << sum << std::endl;
std::cout << "Product: " << product << std::endl;
// int difference = subtract_internal(10, 5); // ERROR: subtract_internal is not exported
return 0;
}
main.cpp
에서 import math;
는 math
모듈에서 내보낸 선언을 사용할 수 있게 합니다. add
와 multiply
는 직접 사용될 수 있지만, subtract_internal
은 접근할 수 없어 모듈의 강력한 캡슐화를 보여줍니다.
이 C++ 모듈 예제는 기본적인 패턴을 보여줍니다. 모듈 인터페이스 단위는 export module
을 사용하여 모듈의 이름과 공개 인터페이스를 선언하는 반면, 모듈 구현 단위는 module
을 사용하여 모듈의 내부 구현에 기여합니다.
기본적인 export
및 import
외에도, C++ 20 모듈은 복잡한 프로젝트 구조와 레거시 코드 변환을 처리하기 위한 여러 고급 기능을 도입합니다. C++23 모듈은 추가적인 개선과 향상된 컴파일러 지원으로 이러한 발전을 계속합니다.
명명된 모듈은 동일한 모듈 이름을 공유하는 모듈 단위(소스 파일)의 모음입니다.
export module MyModule;
)를 가져야 합니다. 이 단위에서 내보낸 내용은 import MyModule
을 가져올 때 가시화됩니다.export module
로 선언된 모든 단위는 인터페이스 단위입니다. 여기에는 기본 인터페이스 단위와 파티션 인터페이스 단위가 포함됩니다.module MyModule;
( export
없음)로 선언된 단위는 구현 단위입니다. 이는 모듈의 내부 로직에 기여하지만, 가져오는 코드에 직접 인터페이스를 노출하지는 않습니다.모듈로 코드를 포팅하는 동안 중요한 과제는 전처리기 매크로에 의존하는 기존 헤더와의 통합입니다. 글로벌 모듈 프래그먼트는 이를 해결합니다.
// mymodule_with_legacy.ixx
module; // Start of global module fragment
#define USE_ADVANCED_FEATURE 1 // Macro defined in global fragment
#include <legacy_header.h> // Include legacy header (hypothetical)
export module mymodule_with_legacy; // End of global module fragment, start of module
import <iostream>; // Import standard library module
export void perform_action() {
#if USE_ADVANCED_FEATURE
std::cout << "Advanced action performed!" << std::endl;
#else
std::cout << "Basic action performed!" << std::endl;
#endif
// Use declarations from legacy_header.h if needed
}
module;
선언은 글로벌 모듈 프래그먼트를 시작합니다. 이 프래그먼트 내의 모든 #include
지시문이나 매크로 정의는 모듈 자체가 정의되기 전에 처리됩니다. 이를 통해 모듈은 모듈의 인터페이스 내에서 매크로 오염 없이 전처리기 종속 레거시 코드와 상호 작용할 수 있습니다. 여기서 정의된 매크로는 이 프래그먼트에 포함된 헤더의 처리에 영향을 미치지만, 나중에 모듈에서 헤더 단위로 가져온 헤더에는 영향을 미치지 않습니다.
개발자들이 구현 세부 사항을 기본 인터페이스 단위 내에 유지하면서 가져오는 코드로부터 완전히 숨기고 싶어 하는 대규모 모듈의 경우, C++20 모듈은 프라이빗 모듈 프래그먼트를 제공합니다.
// main_module.ixx
export module main_module;
export int public_function();
module :private; // Start of private module fragment
// This function is only visible within main_module.ixx itself,
// not to any code that imports 'main_module'.
int internal_helper_function() {
return 36;
}
int public_function() {
return internal_helper_function();
}
module :private;
선언은 모듈의 공개 인터페이스와 비공개 구현 세부 사항 사이의 경계를 표시합니다. 기본 인터페이스 단위 내에서 이 선언 뒤의 코드는 해당 단위와 동일한 명명된 모듈에 속하는 다른 단위에서만 접근할 수 있으며, 외부에서 가져오는 코드에서는 접근할 수 없습니다. 이는 단일 .ixx
파일이 모듈을 나타내면서 공개 및 비공개 섹션을 명확하게 분리하여 캡슐화를 향상시킵니다. 프라이빗 모듈 프래그먼트를 포함하는 모듈 단위는 일반적으로 해당 모듈의 유일한 단위가 될 것입니다.
매우 큰 모듈의 경우, 더 작고 관리하기 쉬운 단위로 분해하는 것이 유익합니다. 모듈 파티션(C++20 모듈)은 이러한 내부 모듈성을 가능하게 합니다. 파티션은 본질적으로 더 큰 명명된 모듈의 서브 모듈입니다.
// large_module_part1.ixx - Partition interface unit
export module large_module:part1; // Declares a partition named 'part1' of 'large_module'
export void do_something_in_part1();
// large_module_part1.cpp - Partition implementation unit
module large_module:part1; // Links this file to the 'part1' partition
void do_something_in_part1() {
// Implementation
}
// large_module_main.ixx - Primary module interface unit for 'large_module'
export module large_module;
// Export-import the partition interface. This makes `do_something_in_part1`
// visible when `large_module` is imported.
export import :part1;
// Can import an implementation partition (if one existed), but cannot export it.
// import :internal_part;
export void do_main_thing() {
do_something_in_part1(); // Can call partition functions directly
}
파티션은 명명된 모듈 자체 내에서만 가시적입니다. 외부 번역 단위는 large_module:part1;
을 직접 가져올 수 없습니다. 대신, 기본 모듈 인터페이스 단위(large_module_main.ixx
)는 export import :part1;
을 사용하여 part1
에서 내보낸 엔티티가 large_module
을 가져오는 코드에 가시화되도록 해야 합니다. 이러한 계층적 구조는 상당한 소프트웨어 개발 프로젝트 내에서 복잡성을 관리하는 견고한 방법을 제공합니다.
C++20 모듈은 또한 헤더 단위를 도입하여 import <header_name>;
또는 import "header_name";
를 사용하여 전통적인 헤더 파일을 가져올 수 있게 합니다. 헤더가 헤더 단위로 가져와질 때, 이는 한 번 파싱되고 그 선언은 모듈과 유사하게 효율적으로 사용 가능해집니다. 결정적으로, 가져오는 번역 단위의 import
문 이전에 정의된 전처리기 매크로는 #include
와 달리 헤더 단위 자체의 처리에 영향을 미치지 않습니다. 이는 더 일관된 동작을 제공하며 대규모 헤더의 컴파일 속도를 크게 향상시킬 수 있습니다.
// my_app.cpp
#define CUSTOM_SETTING 10 // This macro does NOT affect <vector> or "my_utility.h"
import <vector>; // Imports the standard vector header as a header unit
import "my_utility.h"; // Imports a custom header as a header unit (hypothetical)
int main() {
std::vector<int> numbers = {1, 2, 3};
// ...
return 0;
}
이러한 구분은 코드 포팅 노력에 중요합니다. 헤더가 가져오는 번역 단위의 전처리기 상태에 반드시 영향을 받아야 하는 구성을 위해 전처리기 지시문에 의존하는 경우, 글로벌 모듈 프래그먼트가 적절한 해결책입니다. 그렇지 않은 경우, 헤더 단위로 가져오는 것은 #include
에 대한 더 빠르고 깔끔한 대안입니다.
대규모 기존 C++ 프로젝트를 완전한 모듈 시스템으로 코드 변환하고 포팅하는 것은 점진적인 과정일 수 있습니다. 개발자들은 전체 코드베이스를 한 번에 변환할 필요가 없습니다. C++ 모듈은 전통적인 헤더 파일과 원활하게 공존하도록 설계되었습니다.
import
하고 헤더 파일을 #include
할 수 있습니다. 이러한 유연성은 점진적인 채택을 가능하게 하여 한 번에 하나의 구성 요소나 라이브러리를 변환할 수 있습니다.핵심은 실험하고 측정하는 것입니다. 개발자들은 컴파일 시간의 의미 있는 단축과 코드 구성의 개선을 달성하는지 여부에 따라 채택 결정을 내려야 합니다.
C++20 모듈에 대한 지원은 주요 컴파일러 전반에 걸쳐 지속적으로 발전하고 있습니다.
C++ 23 모듈이 더욱 보편화됨에 따라, 추가적인 개선과 향상된 컴파일러 완성도는 전반적인 개발자 경험을 향상시킬 것입니다. 완전하고 최적화된 모듈 지원을 향한 여정은 계속되고 있지만, C++ 20에서 다져진 기반은 C++ 소프트웨어 개발 방식에 심오한 변화를 나타냅니다. 이러한 지속적인 발전은 언어에 더욱 견고하고 효율적인 미래를 약속합니다.
C++ 모듈은 C++ 프로그래밍 관행을 근본적으로 재편하는 혁신적인 기능입니다. 이들은 느린 컴파일, 매크로 간섭, 약한 캡슐화와 같은 헤더 기반 시스템과 관련된 오랜 문제들을 직접적으로 해결합니다. 명시적인 인터페이스 정의와 효율적인 컴파일을 위한 견고한 시스템을 제공함으로써, C++ 모듈은 코드 구성을 향상시키고, 빌드 시간을 단축하며, 관심사의 명확한 분리를 촉진합니다. C++ 20 모듈을 채택하고 C++23 모듈의 향상된 기능을 준비하는 것은 개발자들이 더욱 확장 가능하고, 유지 보수하기 쉬우며, 효율적인 소프트웨어 개발 프로젝트를 구축할 수 있도록 하여 언어에 새로운 시대를 열어줍니다.