C++) Include와 전방선언
촤근 C++ 프로젝트를 접하며 빌드 시간이 너무 길어져서 원인을 분석하게 되었다.
분석 결과, 의존선 문제와 헤더 파일의 남용이 주된 원인이었다.
특히, #include
와 전방 선언을 잘못 구분해서 사용하고 있었던 부분이 성능 병목을 만들고 있었다.
그래서 이번 기회에 두 개념의 차이와 사용법을 정리 해본다.
#include
#include
는 다른 헤더 파일의 내용을 그대로 복붙해서 가져오는 것과 같다.
사용하는 순간, 해당 파일 안에 정의된 구조체, 클래스, 함수 ,템플릿 등 모든 정의가 포함된다.
예를 들자면 Weapon.h
라는 클래스 정의가 있다면
// MyCharacter.h
#include "Weapon.h"
class MyCharacter {
public:
Weapon weapon; // Weapon의 정의가 실제로 필요함
};
이 경우 Weapon
이 어떤 멤버를 가지는지 알아야 하기 때문에 #include
가 반드시 필요했다.
즉, 타입의 내부 구조를 직접 사용할 때는 무조건 #include
를 해야 한다.
전방 선언
전방 선언은 타입의 정의는 몰라도, 존재는 알고 있어야 할 때 사용하는 방법이다.
구조는 모르지만 포인터나 참조 등으로만 다룬다면 전방 선언으로 충분하다.
// MyCharacter.h
class Weapon; // 전방 선언
class MyCharacter {
public:
Weapon* weaponPtr; // 구조 필요 없음
};
이런 방식으로 작성하면 Weapon
의 정의를 헤더 파일에 굳이 포함하지 않아도 된다.
대신 실제로 Weapon의 구현을 사용하는 .cpp
파일에서는 #include
를 해주어야 한다.
// MyCharacter.cpp
#include "Weapon.h"
void MyCharacter::Attack() {
weaponPtr->Fire(); // 이때 구조 필요
}
순환 참조는 전방 선언이 필수
다음과 같은 구조에서 순환 참조 문제가 발생했다.
// A.h
#include "B.h"
class A {
B b; // 구조 필요함
};
// B.h
#include "A.h"
class B {
A a; // 또 구조 필요함
};
이 상황에서는 A와 B가 서로의 정의를 포함하려고 하면서 무한 루프에 빠진다. 이를 해결하기 위해 아래와 같이 전방 선언을 도입했다
// A.h
class B;
class A {
B* b; // OK
};
// B.h
class A;
class B {
A* a; // OK
};
구조는 모르고 포인터만 사용할 것이기 때문에, 이 방식이 문제를 해결해주었다.
컴파일 시간 단축에도 효과적
불필요한 #include
를 전방 선언으로 바꾼 후, 전체 프로젝트 빌드 시간이 약 20~30% 줄어드는 것을 확인했다.
특히 헤더 파일 하나 바꿀 때마다 모든 종속 파일이 재빌드 되던 상황에서 효과가 컸다.
전방 선언을 잘 활용하면 의존성 분리를 더 깔끔하게 유지할 수 있고, 유지보수나 협업 시 충돌도 줄어든다.
결론
상황 | 선택 |
---|---|
구조체 내부를 직접 쓴다 (Weapon weapon ) |
#include |
포인터나 참조만 쓴다 (Weapon* weapon ) |
전방 선언 (class Weapon; ) |
의존성 줄이고 싶다 | 전방 선언 적극 활용 |
.cpp 에서만 사용하는 타입이다 |
.cpp 에 #include 만 작성 |
결국 #include
와 전방 선언은 기능이 아니라 철학의 차이였다.
“어디까지 의존할 것이냐”, “책임은 어디까지 나눌 것이냐” 같은 설계적인 질문과도 연결된다.
이번 기회에 그 차이를 명확히 이해하고, 실전에서 적절하게 사용하는 감각을 익히게 되었다.
댓글남기기