21.4.1.1. ModuleBase<MyModule> 선언의 컴파일 타임 타입 검증 원리
앞서 21.4.1 단원에서 우리는 class CustomApp : public ModuleBase<CustomApp> 이라는 기묘한 CRTP 상속 문법이 ’V-Table 오버헤드를 없애기 위한 흑마술’임을 깨달았다.
하지만 CRTP의 진정한 무서움은 단순한 속도 향상에 그치지 않는다. 이 패턴은 C++ 컴파일러를 **현존하는 가장 까다롭고 엄격한 ‘코드 감찰관(Inspector)’**으로 변신시켜, 개발자가 PX4의 표준 모듈 규격(인터페이스)을 조금이라도 어기면 아예 빌드 자체를 박살 내 버리는 강력한 방어막을 제공한다.
1. 전통적 가상 함수(Virtual)의 런타임 에러 지옥
만약 PX4가 평범한 가상 함수 기반의 인터페이스를 사용했다고 쳐 보자.
부모 클래스에 virtual void Run() = 0; 이라고 선언되어 있는데, 개발자가 실수로 혹은 몰라서 자식 클래스에 void Run() 함수를 만들지 않고 코드를 짰다면 어떻게 될까?
일부 멍청한 구형 컴파일러는 이를 대충 통과시켜버릴 수도 있고, 악질적인 경우엔 펌웨어가 픽스호크에 올라간 뒤 운영체제가 Run()을 호출하려다 ‘순수 가상 함수 호출(Pure Virtual Function Call)’ 패닉을 일으키며 드론을 공중에서 벽돌로 만들어버릴 수도 있다. 즉, 에러의 발견 시점이 늦을수록 버그 잡기는 극도로 고통스러워진다.
2. 템플릿의 조기 경보 시스템(Early Warning System)
하지만 ModuleBase<CustomApp> 패턴에서는 이야기가 전혀 달라진다.
여러분이 부모인 ModuleBase의 템플릿 T 자리에 CustomApp을 꽂아 넣는 순간, 컴파일러는 ModuleBase의 템플릿 소스 코드를 읽어 내려가며 그 즉시(컴파일 타임에) CustomApp의 내부 구조를 엑스레이 찍듯 모조리 스캔하기 시작한다.
ModuleBase 내부에는 대략 이런 뼈대 코드들이 숨겨져 있다.
template<class T>
class ModuleBase {
public:
// ...
int start_command_wrapper() {
// 자식 클래스 T가 반드시 static task_spawn 함수를 가지고 있어야만 한다!
return T::task_spawn(/* ... */);
}
void run_wrapper() {
// 자식 클래스 T가 반드시 인자 없는 Run() 함수를 가지고 있어야만 한다!
static_cast<T*>(this)->Run();
}
};
만약 여러분이 실수로 CustomApp 클래스 설계도 안에 task_spawn 이나 Run 이라는 함수를 빼먹고 타자를 안 쳤다고 가정해 보자.
컴파일러가 코드를 기계어로 번역하다가, 저 템플릿 내부의 래퍼(Wrapper) 함수를 마주치는 순간 즉각적이고 폭력적인 컴파일 에러를 뿜어낸다.
Error: no member named 'task_spawn' in 'CustomApp'
Error: no member named 'Run' in 'CustomApp'
3. ’Duck Typing’의 정적(Static) 구현체
이 현상은 얼핏 파이썬(Python) 같은 스크립트 언어의 **덕 타이핑(Duck Typing: “오리처럼 걷고 오리처럼 꽥꽥거리면 그것은 오리다”)**과 비슷해 보인다.
즉, 부모 클래스(ModuleBase)는 파이썬처럼 “나는 자식이 정확히 무슨 상속 트리를 가졌는지는 모르겠지만, 아무튼 Run()이랑 task_spawn()만 가지고 있으면 오케이!“라고 요구한다.
하지만 파이썬의 덕 타이핑은 프로그램이 실행되다가 중간에 에러가 터지는 치명적 런타임 단점이 있는 반면, PX4의 CRTP 템플릿은 이 덕 타이핑 검사를 C++의 강력한 정적 컴파일 단계로 끌고 내려왔다는 데에 그 우주적인 위대함이 있다.
개발자(여러분)는 픽스호크 보드에 USB 잭을 꽂기도 전에, 즉 make px4_fmu-v6x_default를 친 지 불과 5초 만에 터미널 창 가득 쏟아지는 빨간색 컴파일 에러를 통해 “아, 내가 필수 규격을 빼먹었구나” 라며 즉각적인 참회를 할 수 있게 된다.
이토록 깐깐한 컴파일러의 타입 검증 덕분에, 런타임 시점에는 거추장스러운 방어 코드나 V-Table 룩업 따위는 싹 다 날려버리고 오직 속도(Speed) 지상주의의 기계어만이 남게 된다. 이 컴파일러의 최적화 메커니즘인 ‘인라인(Inline) 확장’ 마술을 다음 장(21.4.1.1.1)에서 계속해서 구경해 보자.