21.4.2.2. 필수 오버라이드 메서드 구현 디테일

21.4.2.2. 필수 오버라이드 메서드 구현 디테일

ModuleBase 시스템의 원자적(Atomic) 방어벽 덕분에, 여러분의 커스텀 모듈은 우주 방사선이 칩셋을 때리지 않는 한 절대 동시에 두 개가 생성되지 않는다.

통제권의 1차 관문을 무사히 통과했다면, 이제 ModuleBase는 자식 클래스인 CustomApp에게 **“그래, 안전은 확보했으니 이제 진짜 네 몸집(객체)을 힙(Heap) 메모리에 부풀리고 시동을 한 번 걸어보거라”**라며 제어권을 넘겨준다.

개발자인 여러분이 반드시 구현(Override)해야만 하는 이 핵심적인 생명주기 제어 통로가 바로 start()stop() 메서드이다. 그리고 이 메서드들을 구현할 때 반드시 지켜야 하는 ’동적 할당(Dynamic Allocation)’과 ’조건 변수(Condition Variable)’에 관한 규칙이 존재한다.

1. 왜 생성자(Constructor)에서 모든 걸 다 하면 안 될까?

C++ 초보자들은 흔히 객체가 태어나는 시점인 생성자 안에서 막무가내로 센서 포트를 열고 100MB짜리 배열을 new로 찍어내곤 한다. 하지만 PX4처럼 메모리가 바닥을 기고 언제 전원이 터질지 모르는 시스템에서는 이러한 안일한 행동은 펌웨어 패닉(Hard Fault)의 지름길이다.

ModuleBase 아키텍처는 **객체의 생성(Creation)**과 **실행(Start)**을 완벽하게 두 단계로 분리해 놓기를 강제한다.

  1. 생성자(CustomApp()): 여기서는 멤버 변수들의 값을 0이나 nullptr로 비워두는 아주 기본적인 초기화만 해야 한다. 메모리나 스레드를 건드려선 안 된다.
  2. start() 메서드: 이곳이 진짜 무대다. 터미널에서 custom_app start 명령이 들어와 모든 스레드 안전성(Thread-safety) 검사가 끝나고 나면, 비로소 이 함수 안에서 운영체제에 막대한 힙 메모리(Heap Memory)를 달라고 수금(Allocation)을 시작한다.

이처럼 생명주기 조작을 명시적인 함수로 빼둔 이유는 **“실패했을 때 원래대로 깨끗하게 되돌리기(Fallback & Cleanup) 위해서”**이다.

2. start()stop() 의 설계 철학

  • start()의 최우선 목적: “메모리가 부족하면 깔끔하게 포기하라.”
    드론의 램이 꽉 찼는데 억지로 메모리를 할당하려 들면 기체가 추락한다. 따라서 start() 함수는 객체를 동적으로 생성할 때마다 반드시 포인터가 nullptr인지 확인하고, 생성이 실패했다면 에러 로그를 띄운 뒤 return -1;로 우아하게 후퇴해야 한다.

  • stop()의 최우선 목적: “스레드가 완전히 숨을 거둘 때까지 기다려라.”
    터미널에서 사용자가 stop을 치면 즉시 런루프가 깨지는 것이 아니다. 루프 구석구석에서 하던 복잡한 행렬 연산이 마무리될 때까지 몇 퍼센트 초(ms)의 유예 시간이 필요하다. stop() 함수는 단순히 ’죽어라’라는 깃발만 꽂아두고 돌아서는 것이 아니라, 대상 스레드가 진짜로 완전히 시체가 되어 메모리에서 내려갈 때까지 기다리는(Wait) 아주 매너 있는 장의사 역할을 수행해야 한다.

이 두 가지 메서드의 코딩 디테일을 파고들기 위해서는 각각 메모리 힙(Heap) 검사 기법과 멀티스레드 대기열(Condition Variable)에 대한 깊은 이해가 필요하다. 이 흥미진진한 로직들을 다음 장들(21.4.2.2.1, 21.4.2.2.2)에서 하나씩 분해해 보자.