사용하는 것만 포함하기 (Include What You Use)¶
소스 파일이나 헤더 파일이 다른 곳에 정의된 심볼을 참조한다면, 해당 심볼의 선언 또는 정의를 제대로 제공하는 헤더 파일을 직접 포함해야 합니다. 다른 이유로 헤더 파일을 포함해서는 안 됩니다.
간접 포함(transitive include)에 의존하지 마세요. 이렇게 하면 더 이상 필요하지 않은 #include를 헤더에서 제거하더라도 클라이언트 코드가 깨지지 않습니다. 이는 관련된 헤더에도 동일하게 적용됩니다. 예를 들어, foo.cc가 bar.h에 정의된 심볼을 사용한다면, foo.h가 bar.h를 포함하고 있더라도 foo.cc는 bar.h를 직접 포함해야 합니다.
이해하기 쉽게 설명하기¶
기본 원칙: "내가 쓰는 건 내가 포함한다"¶
- 어떤 파일이 특정 타입, 함수, 상수 등을 사용한다면, 그 파일은 해당 심볼을 선언/정의하는 헤더를 직접 포함해야 합니다.
- 다른 이유로 헤더 파일을 포함해서는 안된다는 말은, 관성, 습관, 혹시 몰라서 등으로 헤더를 넣으면 안된다는 의미입니다.
- "다른 헤더가 이미 포함하고 있으니까 괜찮겠지"라는 생각은 금지입니다.
왜 간접 포함(transitive include)에 의존하면 안 될까?¶
간접 포함에 의존하면 다음과 같은 문제가 생깁니다:
- A 헤더가 B 헤더를 포함하고, B 헤더가 C 헤더를 포함한다고 해봅시다.
- 어떤
.cc파일이 C에 있는 심볼을 쓰면서 A만 포함하고, C를 직접 포함하지 않는다면, 구조가 바뀌었을 때 문제가 됩니다. - 나중에 B에서 C에 대한
#include를 삭제하면, 해당.cc파일은 갑자기 컴파일이 깨집니다.
즉, 내 파일이 사용하는 심볼이 어디에서 오는지 명확히 하기 위해 직접 포함이 필요합니다.
예시: 나쁜 코드¶
// bar.h
struct Bar {
int value;
};
// foo.h
#include "bar.h"
void ProcessBar(const Bar& bar);
// foo.cc
#include "foo.h" // 여기서는 bar.h를 직접 포함하지 않음
void ProcessBar(const Bar& bar) {
// Bar를 사용하지만, foo.cc는 bar.h를 직접 포함하지 않음
}
위 코드에서 foo.cc는 Bar 타입을 사용하지만, bar.h를 직접 포함하지 않습니다. 만약 foo.h에서 #include "bar.h"를 제거하면, foo.cc는 컴파일 오류가 발생합니다.
예시: 좋은 코드¶
// bar.h
struct Bar {
int value;
};
// foo.h
#include "bar.h"
void ProcessBar(const Bar& bar);
// foo.cc
#include "foo.h"
#include "bar.h" // foo.cc가 직접 bar.h를 포함
void ProcessBar(const Bar& bar) {
// Bar를 안전하게 사용 가능
}
이렇게 하면 foo.h에서 bar.h를 포함하는 방식이 바뀌더라도, foo.cc는 여전히 bar.h를 직접 포함하고 있으므로 안전합니다.
관련 헤더에도 동일한 규칙 적용¶
foo.cc가bar.h의 심볼을 사용한다면,foo.h가bar.h를 포함하고 있더라도foo.cc는 반드시bar.h를 직접 포함해야 합니다.- 이렇게 하면 각 파일이 자신이 필요로 하는 의존성을 명확히 표현하게 되어, 의존 관계가 더 투명해지고 리팩토링도 쉬워집니다.
정리¶
- 원칙: 사용하는 것은 직접 포함한다. (Include What You Use)
- 하지 말 것: "다른 헤더가 대신 포함해 주겠지"라고 기대하지 말 것.
- 효과:
- 불필요한
#include를 제거해도 안전 - 의존 관계가 명확해짐
- 리팩토링과 빌드 속도 개선에 도움이 됨