Blog

ENGINEERING NOTE

[Unreal Engine] Forward declarations

C++로 언리얼 프로젝트를 개발하다 보면, 헤더 파일마다 include가 점점 늘어나 컴파일 시간이 길어지고, 클래스 간 의존성도 복잡해지는 현상이 발생한다.이럴 때 유용하게 사용할 수 있는 것이 바로 전방 선언(Forward Declaration)이다.전방 선언은 어

Unreal Engine

📘 Forward declarations(전방 선언)의 정의/개념

C++로 언리얼 프로젝트를 개발하다 보면, 헤더 파일마다 include가 점점 늘어나 컴파일 시간이 길어지고, 클래스 간 의존성도 복잡해지는 현상이 발생한다. 이럴 때 유용하게 사용할 수 있는 것이 바로 전방 선언(Forward Declaration)이다.

전방 선언은 어떤 클래스나 구조체의 전체 정의를 굳이 include하지 않고, 그 이름만 미리 선언해두는 방식이다. 쉽게 말하면, "이런 클래스가 나중에 정의될 거야"라고 컴파일러에게 미리 알려주는 역할을 한다.


🔧 Forward declarations(전방 선언)의 예시

text
// UMyActor.h

// 전방 선언만 해둠
class UMyComponent;

class AMyActor : public AActor
{
    UMyComponent* MyComponent;  // 포인터만 사용하므로 정의는 필요 없음
};
text
// UMyActor.cpp

#include "UMyComponent.h" // 실제 정의가 필요한 곳에서 include

void AMyActor::BeginPlay()
{
    MyComponent->Activate(); // 이 시점에선 정의가 필요하므로 include 필수
}

✅ Forward declarations(전방 선언)은 왜 필요할까?

헤더 파일에서 어떤 클래스의 포인터나 참조만 필요하다면, 굳이 그 클래스의 모든 내용을 알 필요는 없다.

이때 Forward declarations을 이용하면 다음과 같은 구조적 이점을 얻을 수 있다.

1. 컴파일 시간 단축

  • 헤더 파일에 #include를 많이 쓰면, 의존성이 급격히 늘어나 전체 컴파일 타임이 길어짐.
  • Forward declaration은 클래스의 정의가 필요하지 않은 경우, 헤더 파일 간 의존성을 줄여 컴파일 속도를 대폭 향상시킴.
반대로 #include "MyComponent.h"를 쓰면, MyComponent의 모든 내용을 읽어들이므로 빌드 시간이 증가.

2. 헤더 간의 순환 참조(Circular Dependency) 방지

  • 클래스 A가 클래스 B를 include하고, B도 A를 include하면 순환 참조 문제가 발생.
전방 선언은 헤더 내에서 정의를 끌어오지 않기 때문에 순환 의존을 피할 수 있음.

3. 모듈성과 캡슐화 향상

  • 어떤 타입이 내부 구현에만 필요하고, 헤더의 인터페이스를 구성하지 않는 경우, 전방 선언을 통해 구현을 은닉할 수 있음.
이는 모듈성을 높이고, 다른 클래스가 해당 구현에 불필요하게 의존하지 않도록 방지함.

❗ Unreal Engine에서 Forward declarations을 사용할 때 주의할 점

Forward declarations은 어떤 경우에 사용해야할까?

  • 포인터나 참조만 사용하는 경우엔 전방 선언 가능
  • 멤버로 값(value) 보유하거나, 해당 타입의 메서드를 호출하거나, 상속하는 경우는 반드시 include 필요
text
// ❌ 전방 선언만으로 불가능한 경우 (컴파일 에러 발생)
class MyClass;

class A {
    MyClass MyObject; // 구조체 크기를 알 수 없기 때문에 전방 선언만으로는 불가능
};

언리얼엔진의 공식 문서의 Programming with C++/Coding Standard를 보면 Header Including에 대해 다음과 같은 규칙을 권장하고 있다.

- All headers should protect against multiple includes with the #pragma once directive.
- Note that all compilers we use support #pragma once.
- Try to minimize physical coupling.
- In particular, avoid including standard library headers from other headers.
- Forward declarations are preferred to including headers.
- When including a header, be as fine grained as possible.
- For example, do not include Core.h. Instead, you should include the specific headers in Core that you need definitions from.
- Try to include every header you need directly to make fine-grained inclusion easier.
- Don't rely on a header that is included indirectly by another header you include.
- Don't rely on anything being included through another header. Include everything you need.