21.5.1. 매개변수 클래스 다중 상속 및 초기화 메커니즘
수천 개의 비행 파라미터가 둥둥 떠다니는 PX4의 거대한 파라미터 바다(Server)에 내 커스텀 모듈의 빨대를 꽂으려면, 앞서 언급했듯 ModuleParams라는 특별한 통신 클래스의 힘을 빌려야 한다.
하지만 우리의 CustomApp 클래스는 이미 21.4 단원에서 시스템 스케줄링을 통제받기 위해 부모인 ModuleBase를 상속받은 상태다.
결국 우리는 피할 수 없는 C++의 심화 문법인 **다중 상속(Multiple Inheritance)**의 영역으로 발을 들여놓게 된다.
1. 두 명의 부모를 모시는 모듈 구조
파라미터 업데이트 기능을 포함한 완벽한 PX4 커스텀 모듈의 헤더(Header) 파일은 항상 이런 기괴한 두 줄의 부모 가계도를 자랑한다.
#include <px4_platform_common/module.h>
#include <px4_platform_common/module_params.h>
class CustomApp : public ModuleBase<CustomApp>, public ModuleParams
{
public:
CustomApp();
// ... 메서드들 ...
};
public ModuleBase<CustomApp>: “나는 NSH의 커맨드를 알아듣고, 백그라운드 런루프를 우아하게 돌며, PID를 발급받는 표준 PX4 태스크(Task)의 일원이다.“라는 시스템적 계약이다.public ModuleParams: “나는 파일 시스템(또는 uORB) 어딘가에 저장된 글로벌 파라미터 노드들과 연결되어,update()함수 한 번으로 파라미터 값 변동 이벤트를 쫙 빨아들일(Subscribe) 준비가 되어 있다.“라는 데이터 결속 계약이다.
C++에서 부모가 두 명이라는 것은, 객체가 메모리(Heap)에 태어날 때 두 부모의 생성자(Constructor)가 모두 태동하여 초기화되어야 한다는 뜻이다.
2. 생성자 초기화 리스트(Initializer List)의 의무
C++ 컴파일러는 다중 상속을 받은 자식 클래스(CustomApp)가 인스턴스화 될 때, 개발자가 명시적으로 지시하지 않더라도 묵시적(Implicit)으로 두 부모의 디폴트 생성자(인자가 없는 모양)를 몰래 호출하려 든다.
문제는 ModuleParams라는 부모 클래스가 콧대가 매우 높다는 것이다. 이 클래스는 아무 인자도 받지 않는 디폴트 생성자ModuleParams()를 아예 가지고 있지 않다.
왜냐하면 글로벌 파라미터 서버에 무턱대고 내 변수들을 동기화시키려면, “어떤 놈(모듈)이 나를 호출하고 있는지” 그 식별자 꼬리표(Pointer)가 최소한 하나는 필요하기 때문이다.
따라서 우리는 CustomApp의 C++ 구현부(.cpp 파일)에서 생성자를 작성할 때, C++ 고유의 문법인 **생성자 초기화 리스트(Initializer List)**를 사용하여 부모인 ModuleParams의 입안으로 강탈하듯 인자를 쑤셔 넣어 주어야 한다.
// 이것은 컴파일 에러를 뿜는다! (ModuleParams의 디폴트 생성자가 없기 때문)
CustomApp::CustomApp()
{
// ...
}
// 이것이 정답이다! (초기화 리스트 콜론(:))
CustomApp::CustomApp() :
ModuleParams(nullptr) // 또는 부모 객체의 주소
{
// ...
}
저 콜론(:) 뒤에 붙은 ModuleParams(nullptr) 이라는 투박한 호출 한 줄에는 파라미터 트리를 엮어내는 오묘한 역학 관계가 얽혀 있다.
왜 하필 nullptr을 넘겨주는 것이며, 이 값이 내부적으로 어떻게 글로벌 파라미터 연결 고리(Linked List)로 이어지는지에 대한 심층적인 원리를 다음 단원(21.5.1.1)에서 낱낱이 파헤쳐보겠다.