이번에 부트캠프에서 주어진 과제를 구현하다가 전방 선언에 대해 실수한 부분이 있다보니 이 부분에 대해서 바로 잡아보려고 한다.
전방 선언(Forward Declaration)은 "컴파일러에게 이런 클래스(또는 구조체, 함수 등)가 있으니 알아만 둬!" 라고만 말하고 크기나 구현부 등은 알려주지 않는 것을 의미한다.
아마 C언어 배울 때 함수를 배우면 아래와 같이 정의만 맨 위에 작성하는 경우가 있는데, 이게 바로 전방 선언이다.
// 함수의 전방 선언 (리턴 타입, 이름, 매개변수 타입만 명시)
int Add(int a, int b);
int main() {
int result = Add(3, 5);
return 0;
}
// 실제 정의
int Add(int a, int b) {
return a + b;
}
불편하게 정의와 동시에 구현을 하면 되는거 아닌가? 싶지만 아래의 경우를 보면 이유가 드러난다.
// A.h
#include "B.h"
class A { B* b; };
// B.h
#include "A.h"
class B { A* a; };
#include는 전처리 타임(컴파일 이전 단계)에 내용을 복붙하는 지시문이다. 즉, 위 코드를 전처리 시간에 관찰해보면 다음과 같다.
// ...
class A { B* b; };
class B { A* a; };
class A { B* b; };
class B { A* a; };
class A { B* b; };
// ...
위와 같이 계속 구문이 반복되어서 컴파일 타임에 무한 루프에 빠져서 에러가 난다.
이를 해결하려면 다음과 같이 하면 된다.
// A.h
class B; // 전방 선언
class A { B* b; };
// B.h
class A; // 전방 선언
class B { A* a; };
즉, 전방 선언을 하면 이러한 부분이 있으니 컴파일 타임에서는 그냥 넘어가고 링킹 작업에서 구현부를 찾아 나서겠다는 것을 의미한다.
// --- Player.h ---
#include "Weapon.h"
#include "Armor.h"
#include "Potion.h"
class Player {
private:
Weapon* myWeapon;
Armor* myArmor;
};
언리얼 C++에서도 볼 수 있지만 보통 정의는 헤더 파일(.h)에, 구현은 소스 파일(.cpp) 작성한다.
헤더 파일에서 위와 같이 include를 하면 Player는 여러 개의 헤더에 의존된 상태가 된다.
Dwarf라는 클래스를 구현한다고 하자. Dwarf는 Player에 의존하지만 Potion과는 관계가 없다고 하면 Dwarf는 쓰지도 않는 Potion 헤더파일을 컴파일하게 되는 것이다.
이는 장기 프로젝트로 갈 수록 치명적인 속도 저하로 다가올 수 있다.
// --- Player.h ---
class Weapon;
class Armor;
class Potion;
class Player {
private:
Weapon* myWeapon;
Armor* myArmor;
Potion* myPotion;
// ...
};
// --- Player.cpp ---
#include "Weapon.h"
#include "Armor.h"
#include "Potion.h"
위와 같이 전방 선언을 하면 컴파일 타임은 통과하게 되고 구현부는 링킹 과정에서 알아서 찾기 때문에 불필요한 컴파일 과정을 줄일 수 있다.
그렇다고 전방 선언을 함부로 사용하면 아래와 같은 에러를 볼 수 있다.
/* Case 1 */
class Inventory; // 전방 선언
class Character {
private:
Inventory myInventory; // (X) 컴파일 에러!
};
/* Case 2 */
class Monster; // 전방 선언
void Attack() {
Monster* mob;
mob->TakeDamage(10); // (X) 컴파일 에러!
}
class BossMonster : public Monster { // (X) 컴파일 에러!
};
위에서 언급한 케이스와 같이 컴파일러가 크기와 구현부를 꼭 알아야 할 때는 전방 선언이 통하지 않는다.
즉, 이름표의 역할만 할 수 있는 포인터, 참조자를 사용할 때나 전방 선언을 쓸 수 있음을 기억해두자.