출시·고도화 중
C++ 안내서 · 4/6
프로그램이 커지면 코드를 여러 파일로 나누고, 이름 충돌을 피하고, 외부 라이브러리를 가져와야 합니다. C++에서는 오랫동안 헤더와 소스 파일, 네임스페이스, 빌드 시스템이 이 역할을 나눠 맡아 왔습니다. 이 장에서는 이 전통적인 방식과 함께 CMake 타깃, 패키지 관리자, 그리고 C++20 모듈을 간단히 살펴봅니다.
보통 선언은 헤더(.h, .hpp)에, 정의는 소스(.cpp)에 둡니다. 다른 파일은 헤더를 #include해서 선언만 보고, 실제 구현은 링크 단계에서 연결됩니다.
// math_utils.hpp
#pragma once
namespace mathx {
int gcd(int a, int b);
double mean(const int* values, int count);
}// math_utils.cpp
#include "math_utils.hpp"
namespace mathx {
int gcd(int a, int b) {
while (b != 0) {
int t = a % b;
a = b;
b = t;
}
return a;
}
double mean(const int* values, int count) {
if (count == 0) return 0.0;
long long sum = 0;
for (int i = 0; i < count; ++i) sum += values[i];
return static_cast<double>(sum) / count;
}
}#pragma once는 같은 헤더가 한 번역 단위에 두 번 포함되지 않게 막습니다. 표준 문법은 아니지만 주요 컴파일러가 모두 지원하며, 표준 방식인 #ifndef/#define 인클루드 가드를 써도 됩니다.
각 .cpp 파일은 따로 컴파일되어 오브젝트 파일이 되고, 링커가 이들을 하나의 실행 파일로 묶습니다. 하나의 파일만 고쳤다면 그 파일만 다시 컴파일하면 됩니다.
g++ -std=c++20 -c math_utils.cpp -o math_utils.o
g++ -std=c++20 -c main.cpp -o main.o
g++ main.o math_utils.o -o app함수 정의가 어디에도 없으면 "undefined reference" 같은 링크 오류가 납니다. 반대로 같은 함수를 헤더에 정의하고 여러 소스에서 포함하면 "multiple definition" 오류가 납니다. 헤더에 함수 본문을 두려면 inline을 붙이거나 템플릿·클래스 안의 멤버 함수로 정의합니다.
네임스페이스는 이름에 소속을 붙여 충돌을 막습니다. 표준 라이브러리는 모두 std 안에 있습니다.
#include <iostream>
#include "math_utils.hpp"
namespace app::config { // C++17 중첩 네임스페이스 표기
constexpr int retries = 3;
}
int main() {
std::cout << mathx::gcd(12, 18) << '\n'; // 6
std::cout << app::config::retries << '\n';
using mathx::gcd; // 이 이름만 가져오기
std::cout << gcd(8, 12) << '\n';
}헤더 파일에서 using namespace std;를 쓰면 그 헤더를 포함하는 모든 파일에 이름이 퍼지므로 피합니다. 소스 파일 안의 좁은 범위에서 필요한 이름만 using으로 가져오는 편이 안전합니다.
현대적인 CMake는 타깃 단위로 생각합니다. 라이브러리와 실행 파일을 타깃으로 만들고, 타깃 사이의 의존 관계와 포함 경로, 컴파일 옵션을 target_* 명령으로 연결합니다.
cmake_minimum_required(VERSION 3.20)
project(calc LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_library(mathx src/math_utils.cpp)
target_include_directories(mathx PUBLIC include)
add_executable(calc src/main.cpp)
target_link_libraries(calc PRIVATE mathx)PUBLIC은 이 설정이 해당 타깃을 쓰는 쪽에도 전달된다는 뜻이고, PRIVATE은 그 타깃 내부에서만 쓴다는 뜻입니다. 위 예에서 calc는 mathx에 링크하는 것만으로 include 경로까지 함께 받습니다.
C++에는 언어 공식 패키지 관리자가 없지만, 널리 쓰이는 도구가 있습니다.
| 도구 | 특징 |
|---|---|
| vcpkg | Microsoft가 관리. vcpkg.json 매니페스트와 CMake 툴체인 파일로 연동 |
| Conan | Python 기반. conanfile.txt 또는 conanfile.py로 의존성과 생성기를 지정 |
| CMake FetchContent | 별도 도구 없이 CMake가 Git 저장소 등에서 소스를 받아 함께 빌드 |
vcpkg 매니페스트 모드에서는 프로젝트 루트에 vcpkg.json을 두고 CMake를 구성할 때 툴체인 파일을 지정합니다.
# vcpkg.json 예: { "name": "calc", "dependencies": [ "fmt" ] }
cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=<vcpkg 경로>/scripts/buildsystems/vcpkg.cmake
cmake --build build그 뒤 CMakeLists.txt에서 find_package(fmt CONFIG REQUIRED)와 target_link_libraries(calc PRIVATE fmt::fmt)로 라이브러리를 씁니다. Conan은 conan install . --build=missing으로 의존성을 준비한 뒤, 생성된 툴체인 파일로 CMake를 구성합니다.
C++20은 헤더를 텍스트로 복사해 넣는 #include 방식의 단점(느린 빌드, 매크로 누출)을 줄이기 위해 모듈을 도입했습니다. 모듈은 export한 이름만 바깥에 보여 줍니다.
// greet.cppm (모듈 인터페이스 파일; 확장자는 도구마다 .ixx 등으로 다를 수 있습니다)
module; // 전역 모듈 조각: 기존 헤더는 여기서 포함합니다
#include <string>
export module greet;
export std::string greet(const std::string& name) {
return "Hello, " + name;
}사용하는 쪽에서는 import greet;라고 씁니다. C++23에서는 표준 라이브러리 전체를 import std;로 가져올 수 있습니다. 다만 모듈은 컴파일러와 빌드 시스템의 지원이 아직 고르지 않으므로, 실제 프로젝트에서는 사용하는 컴파일러와 CMake 버전의 문서를 먼저 확인하고 도입하는 것이 좋습니다. 대부분의 기존 라이브러리는 여전히 헤더로 배포됩니다.
using namespace를 피합니다.add_library/add_executable 타깃과 target_link_libraries로 구성합니다.
댓글 0개
로그인 · 로그인하면 댓글을 남길 수 있습니다.
첫 댓글을 남겨 보세요.