21.5.1.1.1. `ModuleParams(nullptr)` 호출을 통한 전역 파라미터 리스트 연결 원리

21.5.1.1.1. ModuleParams(nullptr) 호출을 통한 전역 파라미터 리스트 연결 원리

앞 단원에서 우리는 C++ 다중 상속의 반항을 잠재우기 위해 생성자 초기화 리스트에 ModuleParams(nullptr) 이라는 주술과도 같은 코드를 밀어 넣었다.

대체 저 nullptr 은 무엇을 의미하며, 파라미터 서버는 저 텅 빈 포인터를 가지고 어떻게 우리 모듈 안의 변수들을 찰떡같이 업데이트해 주는 것일까?

이 마법을 이해하려면 ModuleParams 클래스 내부에 숨겨진 단일 연결 리스트(Singly Linked List) 구조를 짚고 넘어가야 한다.

1. 파라미터 트리 구조의 최상단(Top-Level) 노드 생성

PX4 파라미터 아키텍처는 개별 변수(ParamInt, ParamFloat)들이 흩어져서 굴러다니지 않고, 일종의 ‘가족 계보(Linked List)’ 형태로 엮여 있도록 설계되어 있다.

여러분이 부모 생성자에 넘겨준 저 첫 번째 인자는 사실 **“내(현재 태어나는 객체)가 누구 밑으로 들어갈 것인지 부모 노드(Parent Node)의 주소를 적어내라!”**라는 요구 사항이다.

// ModuleParams.hpp 의 내부 로직을 단순화한 모습
class ModuleParams {
public:
    ModuleParams(ModuleParams *parent) {
        if (parent != nullptr) {
            // 부모가 있으면 그 부모의 링크드 리스트 꼬리에 나를 편입시킨다.
            parent->add_child(this);
        } else {
            // 내가 족보의 시작점(Top-Level) 이구나!
            // (최상단 노드로서 시스템 전역 파라미터 캐시와 직결됨)
        }
    }
};

우리가 CustomApp 모듈의 생성자에서 ModuleParams(nullptr)를 넘긴 이유는, **우리 모듈 자체가 파라미터 계보의 가장 꼭대기(Top-Level)에 위치한 독립된 어른(Root Node)**임을 선언하는 행위다.

2. 하위 클래스를 위한 this 포인터 물려주기

이 구조는 모듈이 굉장히 크고 복잡해질 때 그 진가를 발휘한다.
만약 CustomApp 안에 센서를 담당하는 SensorManager라는 별도의 C++ 클래스를 만들었고, 이 매니저 내부에도 또 파라미터들이 잔뜩 선언되어 있다고 가정해 보자.

SensorManager 역시 똑같이 public ModuleParams를 상속받겠지만, 이 객체가 태어날 때는 nullptr을 넘기면 안 된다.

// CustomApp.cpp
CustomApp::CustomApp() :
    ModuleParams(nullptr),
    _sensor_manager(this) // 나의 주소(this)를 자식의 부모 포인터로 넘겨줌!
{
}

// SensorManager.cpp
SensorManager::SensorManager(ModuleParams *parent) :
    ModuleParams(parent) // CustomApp 밑으로 탯줄을 연결!
{
}

이렇게 부모의 주소(Pointer)를 물고 물려주는 릴레이 상속 덕분에, 픽스호크 메모리 안에는 거대한 **파라미터 트리(Tree)**가 형성된다.

3. update()의 낙수 효과(Trickle-Down)

이 거대한 트리의 가장 큰 이점은 바로 파라미터 업데이트의 우아함(Elegance)에 있다.
사용자가 QGroundControl에서 파라미터를 변경하여 uORB 업데이트 메시지가 터미널에 도착하면, 메인 런루프는 그저 최상단 객체인 CustomAppupdateParams()를 딱 한 번만 호출해 주면 된다.

// CustomApp의 런루프 내 어딘가...
if (parameter_update_sub.updated()) {
    // 이 한 줄의 호출이 낙수 효과를 발생시킨다.
    updateParams(); 
}

updateParams() 가 호출되는 순간, 최상단 노드는 자신에게 묶인(nullptr 선언으로 시작된) 직접적인 변수들을 모두 갱신한 뒤, 아까 탯줄(this)을 이어준 하위 객체(SensorManager)들의 updateParams()를 **연쇄적으로 호출(Cascade)**해 버린다.

결과적으로 단 한 번의 호출 파도(Wave)가 족보를 타고 내려가며 수백 개의 분산된 클래스 변수들을 일제히, 그리고 원자적으로 동기화시키는 기적을 이뤄내는 것이다.

이제 우리는 모듈 객체가 파라미터 트리의 꼭대기로 등록되는 족보 꼬기 과정을 완벽히 이해했다. 그렇다면 이 족보 안에서 진짜 알맹이 변수들(int, float)은 도대체 어떤 문법으로 엮이는 것일까? 다음 장(21.5.2)에서 그 기상천외한 매크로(Macro)의 세계, **DEFINE_PARAMETERS**를 만나보자.