촤근 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와 전방 선언은 기능이 아니라 철학의 차이였다.

“어디까지 의존할 것이냐”, “책임은 어디까지 나눌 것이냐” 같은 설계적인 질문과도 연결된다.

이번 기회에 그 차이를 명확히 이해하고, 실전에서 적절하게 사용하는 감각을 익히게 되었다.

태그:

카테고리:

업데이트:

댓글남기기