HOWTO · C++

C++의 #pragma once: include guard와 이식성

#pragma once 또는 include guard로 C++ 헤더의 중복 포함을 방지하고 적절한 방법을 선택합니다.

#pragma once는 같은 번역 단위에서 헤더 파일을 최대 한 번만 처리하도록 구현에 지시하는 전처리기 지시문입니다. 프로젝트가 지원하는 모든 컴파일러가 이를 구현한다면 일반 C/C++ 헤더 맨 앞에 둡니다. 표준 이식성, 알 수 없는 컴파일러 또는 기존 규약이 필요하면 고유한 #ifndef/#define include guard를 사용하세요. 두 방법 모두 프로그램 전체에서 헤더를 한 번만 쓰게 하는 것은 아닙니다.

선언 앞에 #pragma once 배치

// config.hpp
#pragma once

class Config {
public:
    int port() const { return 8080; }
};

세미콜론은 필요 없습니다. 전처리기는 컴파일 전에 #include를 처리하므로, 이 지시문은 한 번역 단위를 만드는 동안 같은 헤더 텍스트를 다시 처리하지 않게 합니다. GCC, Clang, MSVC에서 널리 구현되지만 ISO C/C++ 표준 지시문은 아닙니다. GCC의 once-only header 문서를 참고하되, 실제로 지원을 약속한 도구 체인을 확인하세요.

직접 및 간접 포함 확인

다음 검증된 세 파일은 config.hppserver.hpp를 통해, 그리고 main.cpp에서 직접 포함합니다.

// server.hpp
#pragma once
#include "config.hpp"

class Server { Config config_; };
// main.cpp
#include "server.hpp"
#include "config.hpp"

int main() {
    return Config{}.port() == 8080 ? 0 : 1;
}

세 파일을 같은 디렉터리에 저장하고 실행합니다.

g++ -std=c++17 -Wall -Wextra main.cpp -o app
./app

config.hpp#pragma once가 있으면 출력이 없고 ./app은 상태 0으로 끝납니다. g++ (Ubuntu 15.2.0-16ubuntu1) 15.2.0에서 확인했습니다. 해당 줄만 제거하면 GCC는 상태 1로 끝나며 직접 포함과 이전 간접 포함으로 인한 Config 재정의를 보고합니다. 이는 번역 단위별 동작이며 MSVC/Clang에서 따로 실행한 결과는 아닙니다.

표준 include guard 사용

// config.hpp
#ifndef EXAMPLE_CONFIG_HPP
#define EXAMPLE_CONFIG_HPP

class Config {
public:
    int port() const { return 8080; }
};

#endif  // EXAMPLE_CONFIG_HPP

첫 포함에서 매크로를 정의하고 이후에는 본문을 건너뜁니다. 프로젝트, 디렉터리, 파일 이름을 조합한 설명적이고 고유한 이름을 고르세요. CONFIG_H는 다른 헤더와 충돌할 수 있고, __를 포함하거나 _ 뒤에 대문자가 오는 이름은 구현에 예약되어 있습니다.

#include는 텍스트 포함입니다. C++ 선언을 검사하기 전에 전처리기가 지시문을 헤더 텍스트로 바꿉니다. 예제에서 config.hpp는 먼저 server.hpp를 거쳐, 다음에는 main.cpp에서 직접 들어오므로 보호가 없으면 컴파일러는 Config 정의 두 개를 받습니다. 따라서 보호는 우연히 포함하는 소스 파일만이 아니라 헤더 자체에 둬야 합니다. 이는 컴파일 오류이며 별도로 컴파일된 파일의 링크 오류와 다릅니다.

방법 선택

상황 선택 이유
대상 컴파일러가 모두 #pragma once를 구현 #pragma once 짧고 guard 매크로 충돌이 없다.
공개 라이브러리, 알 수 없는 컴파일러, 엄격한 이식성 include guard 표준 전처리기 지시문이다.
리포지터리 규약이 있음 기존 규약 헤더 유지보수가 일관된다.
X-macro 목록처럼 의도적으로 여러 번 포함하는 헤더 기본적으로 둘 다 사용하지 않음 반복 포함 자체가 목적이다.

보통 모든 헤더에 두 방법을 함께 넣지 마세요. 하나면 충분하고 컴파일러도 관례적인 guard를 인식합니다. 보편적인 빌드 속도 향상을 주장하지 말고 필요하면 측정하세요. #pragma once는 구현의 파일 동일성 판정에 의존하므로 별칭, 생성 파일, 네트워크 파일 시스템, 특이한 경로가 경계 조건입니다. guard는 이를 피하지만 매크로가 고유해야 합니다.

헤더 보호가 해결하지 못하는 문제

보호는 번역 단위별로 동작하며 모든 다중 정의 링크 오류나 One Definition Rule (ODR)을 해결하지 않습니다. 헤더의 비-inline 일반 함수 정의는 각 .cpp에서 외부 정의를 만들 수 있으므로 일반 정의는 .cpp에 두거나 필요한 경우에만 inline 또는 템플릿을 사용하세요. 두 타입이 완전한 정의를 요구하는 순환 의존성도 해결하지 못합니다. 포인터나 참조에는 전방 선언을 쓰거나 구현을 옮기세요. C++20 모듈은 별개이며 import는 헤더 텍스트를 포함하지 않습니다.

요약

확장 지원을 받아들일 수 있으면 #pragma once, 표준 이식성에는 고유한 include guard를 사용하세요. 직접·간접 경로를 확인하고 ODR, 순환, 의도적 반복 포함, 모듈 문제는 별도로 진단합니다.