PX4 MAVSDK – C++ Programming 제 [2편] MAVSDK 활용을 위한 C++ 핵심 문법

안녕하세요! 마케팅팀 에이든(Aiden)입니다. 지난 1편에서는 자율 비행 로봇 시스템에서 MAVSDK가 가지는 의미와 전반적인 아키텍처에 대해 알아보았습니다.

본격적으로 드론을 제어하는 코드를 작성하기에 앞서, 우리가 사용할 도구인 C++에 대해 짚고 넘어갈 필요가 있습니다. 파이썬(Python)과 같은 스크립트 언어에 익숙한 대학생이나 연구원분들에게 C++은 다소 무겁고 복잡하게 느껴질 수 있습니다. 하지만 MAVSDK 코어는 현대적인 C++17 표준으로 작성되어 있으며, 고성능 실시간 제어가 필수적인 드론 분야에서 C++은 선택이 아닌 필수입니다.

이번 연재를 통해 MAVSDK를 다루기 위해 반드시 알아야 할 C++의 핵심 문법만 간결하게 정리해드리겠습니다.


1. C++의 기본기 다지기: 입출력, 문자열, 그리고 동적 배열

C언어의 printf, scanf, 그리고 char 배열의 불편함을 C++은 아주 우아하게 해결합니다.

1) 안전하고 직관적인 입출력 (std::cout, std::cin)

C++에서는 꺾쇠괄호(<<, >>)를 이용해 데이터가 흐르는 ‘스트림(Stream)’ 형태로 입출력을 처리합니다. 자료형을 명시하는 %d, %s를 신경 쓸 필요가 없어 매우 직관적입니다.

C++
#include <iostream>
#include <string>

int main() {
    std::string name;
    std::cout << "드론의 이름을 입력하세요: "; 
    std::cin >> name; // 사용자 입력 받기
    std::cout << "연결된 드론: " << name << std::endl; // 출력 및 줄바꿈
    return 0;
}
  • std::endl은 줄바꿈과 동시에 출력 버퍼를 비워주는 역할을 합니다.

2) C++ 문자열의 표준: std::string

C언어에서는 문자열을 다룰 때 char name; 처럼 배열 크기를 미리 정해야 했고, 합치거나 길이를 구할 때 strcat(), strlen() 같은 번거로운 함수를 써야 했습니다. C++의 std::string은 크기가 자동으로 조절되며, + 연산자로 쉽게 문자열을 붙일 수 있습니다. MAVSDK에서 드론의 연결 URL(예: "udpin://0.0.0.0:14540")을 다룰 때 아주 유용하게 사용됩니다.

3) 동적 배열의 끝판왕: std::vector

드론에 다수의 웨이포인트(경유지) 임무를 내릴 때, 웨이포인트의 개수는 비행마다 달라집니다. 이때 C언어의 고정 배열(int arr;)은 한계가 있습니다. C++의 std::vector크기가 자동으로 조절되는 동적 배열입니다.

C++
#include <vector>
#include <iostream>

int main() {
    std::vector<int> waypoints; // 크기가 0인 동적 배열 생성
    waypoints.push_back(100);   // 맨 뒤에 100 추가
    waypoints.push_back(200);   // 맨 뒤에 200 추가

    // 범위 기반 for문 (요소를 하나씩 꺼내옴)
    for (int wp : waypoints) {
        std::cout << "웨이포인트 고도: " << wp << "m\n";
    }
    std::cout << "총 웨이포인트 개수: " << waypoints.size() << "\n"; // 2 출력
    return 0;
}

push_back()으로 요소를 추가하고, size()로 개수를 확인하며, 범위를 지정하는 for문으로 쉽게 순회할 수 있습니다. MAVSDK 미션 플러그인에서 std::vector<Mission::MissionItem> 형태로 수많은 임무 지점을 담을 때 반드시 사용됩니다.


2. 이름공간(namespace)과 객체지향(OOP), 그리고 파일 분할

1) 이름 충돌을 막아주는 이름공간 (namespace)

앞선 코드에서 계속 등장한 std::는 무엇일까요? 이는 **이름공간(namespace)**이라는 개념입니다. 수많은 개발자가 참여하는 큰 프로젝트에서는 함수나 변수 이름이 겹칠 수 있습니다. 이를 방지하기 위해 구역을 나누는 것입니다.

  • std::는 C++ 표준 라이브러리의 이름공간입니다.
  • MAVSDK의 모든 기능은 mavsdk::라는 이름공간 안에 들어 있습니다.

매번 쓰기 귀찮다면 using namespace mavsdk; 처럼 선언하여 생략할 수 있지만, 복잡한 프로젝트나 헤더 파일에서는 충돌 방지를 위해 명시적으로 적어주는 것이 좋습니다.

2) 클래스(Class)와 객체지향 프로그래밍

클래스는 “데이터(멤버 변수)”와 “기능(멤버 함수)”을 하나로 묶은 설계도입니다. MAVSDK는 완벽한 객체지향 라이브러리입니다. 예를 들어 드론(System) 객체가 있고, 이 드론이 수행할 수 있는 동작(Action) 객체가 따로 분리되어 있죠.

C++
class Drone {
private:
    int battery; // 외부에서 직접 접근 불가 (데이터 보호)

public:
    Drone() { battery = 100; } // 생성자: 객체 생성 시 자동 실행
    
    void fly() { // 멤버 함수
        if (battery > 10) {
            std::cout << "비행을 시작합니다.\n";
            battery -= 10;
        }
    }
};

클래스를 사용하면 내부의 복잡한 연산(Private)은 숨기고, 사용자가 꼭 필요한 기능(Public)만 사용할 수 있도록 인터페이스를 깔끔하게 유지할 수 있습니다.

3) 헤더 파일(.h)과 구현 파일(.cpp) 분리

C++ 프로젝트는 보통 코드를 **선언부(.h)**와 **구현부(.cpp)**로 나눕니다.

  • .h 파일: 클래스의 구조와 함수의 껍데기만 선언합니다. (어떤 기능이 있는지 설명서 역할)
  • .cpp 파일: 실제 함수가 어떻게 동작하는지 내부 로직을 작성합니다. 이렇게 분리하면 코드를 재사용하기 쉽고, 다른 개발자가 헤더 파일만 보고도 기능을 쉽게 파악할 수 있습니다.

3. 메모리와 성능의 핵심! 포인터(*)와 참조(&)

C++을 강력하면서도 어렵게 만드는 주범이지만, 로보틱스에서는 성능 최적화를 위해 반드시 이해해야 합니다.

1) 포인터 vs 참조

  • 포인터(*): 메모리의 ‘주소’를 저장하는 변수입니다. 값이 비어있을 수 있습니다(nullptr).
  • 참조(&): 이미 존재하는 변수의 **’별명(Alias)’**을 만듭니다. 반드시 대상을 지정해야 하며 비어있을 수 없습니다. C++에서는 포인터보다 안전하고 사용하기 쉬운 **참조(&)**를 기본으로 사용합니다.

2) 상수 참조 (const &) – 성능 최적화의 비밀!

드론으로부터 엄청난 양의 텔레메트리(원격 측정) 데이터를 받는다고 상상해 보세요. 이 거대한 데이터를 함수로 전달할 때마다 메모리에 ‘복사’한다면 CPU와 메모리가 낭비될 것입니다.

C++
// ❌ 값 전달: 거대한 데이터가 통째로 복사됨 (느리고 무거움)
void printTelemetry(TelemetryData data) { ... }

// ✅ 상수 참조 전달: 데이터 복사 없이 원본의 주소만 공유하며, 수정은 불가 (빠르고 안전함)
void printTelemetry(const TelemetryData& data) { ... }

const &(상수 참조)를 사용하면 데이터 복사본을 만들지 않아 실행 속도가 빠르며, 동시에 원본 데이터가 변경되는 것을 막아주어 매우 안전합니다. MAVSDK API 곳곳에서 이 패턴을 발견하실 수 있을 것입니다.


4. 코드 재사용의 마법: 템플릿(Template)과 STL

1) 템플릿 (Template)

데이터 타입(자료형)에 의존하지 않고, 하나의 코드로 여러 자료형을 처리할 수 있게 해주는 ‘제너릭 프로그래밍(Generic Programming)’의 핵심입니다.

C++
template <typename T>
T getMax(T a, T b) {
    return (a > b) ? a : b;
}

// 하나의 함수로 정수, 실수 모두 처리 가능!
int main() {
    std::cout << getMax<int>(10, 20);       // 20 출력
    std::cout << getMax<double>(3.14, 5.5); // 5.5 출력
}

드론의 X, Y, Z 좌표를 정밀도에 따라 int, float, double 등 다양한 타입으로 다뤄야 할 때, 템플릿을 쓰면 함수를 한 번만 작성해도 됩니다.

2) STL (Standard Template Library)

앞서 배운 std::vector가 바로 이 템플릿을 기반으로 만들어진 C++ 표준 라이브러리(STL)입니다. STL은 데이터를 담는 컨테이너(Container), 데이터를 순회하는 반복자(Iterator), 그리고 정렬이나 탐색을 도와주는 **알고리즘(Algorithm)**으로 구성되어 있습니다.

C++
#include <vector>
#include <algorithm> // 알고리즘 헤더

std::vector<int> v = {5, 1, 3, 4, 2};
std::sort(v.begin(), v.end()); // 오름차순 자동 정렬! [31]


5. MAVSDK 실전 팁! auto와 람다(Lambda) 함수

현대 C++(C++11 이상) 문법 중 MAVSDK 코드에서 숨 쉬듯이 등장하는 두 가지 기능입니다.

1) 컴파일러가 알아서 척척! auto

auto변수의 자료형을 컴파일러가 알아서 추론하게 하는 마법의 키워드입니다. MAVSDK의 함수들은 반환 타입이 매우 길고 복잡한 경우가 많습니다.

C++
// ❌ 복잡한 타입 명시
mavsdk::ConnectionResult result = mavsdk.add_any_connection("udp://:14540");

// ✅ auto를 활용한 깔끔한 코드
auto result = mavsdk.add_any_connection("udp://:14540");

우변의 결과값을 보고 컴파일러가 result의 타입을 자동으로 ConnectionResult로 맞춰줍니다.

2) 이름 없는 일회용 함수, 람다(Lambda)

드론 프로그래밍은 “비행 모드가 바뀌면 알려줘!”, “새로운 GPS 데이터가 들어오면 이 코드를 실행해 줘!”와 같은 비동기 콜백(Callback) 처리의 연속입니다. 이때 매번 새로운 함수를 바깥에 정의하는 것은 번거롭습니다. 람다 함수 [](){}를 쓰면 함수 안에서 즉석으로 콜백 함수를 정의할 수 있습니다.

C++
// 드론의 위치 정보가 업데이트될 때마다 즉석에서 실행되는 람다 함수 콜백 [36]
telemetry.subscribe_position([](Telemetry::Position position) {
    std::cout << "현재 고도: " << position.relative_altitude_m << "m\n";
});
  • [] (캡처): 외부 변수를 람다 함수 내부로 가져올 때 씁니다.
  • (Telemetry::Position position): 콜백으로 들어오는 데이터입니다.
  • { ... }: 실제 실행될 코드입니다.

이 람다 함수 패턴은 앞으로 MAVSDK로 비동기 제어를 할 때 가장 자주 마주치게 될 구조이니 눈에 꼭 익혀두시기 바랍니다!


지금까지 MAVSDK C++ 프로그래밍을 위해 꼭 필요한 C++의 핵심 문법인 입출력, vector, 클래스, 참조(&), 템플릿, 그리고 auto와 람다 함수까지 살펴보았습니다.

이 개념들이 지금 당장은 100% 이해되지 않더라도 괜찮습니다. 앞으로 이어질 연재에서 실제 드론 제어 코드를 작성하며 이 문법들이 어떻게 유기적으로 동작하는지 직접 체감하게 되실겁니다.

다음 연재로 돌아올 MAVSDK C++ 설치 및 SITL 시뮬레이션 환경 구축에서는 복잡한 이론을 넘어, 실제로 내 컴퓨터에 가상의 드론(Gazebo SITL)을 띄우고 코드로 연결할 준비를 해보겠습니다.


YOUTUBE 강좌

재생


작성자: 에이든(Aiden), 쿼드(QUAD) 드론연구소 마케팅팀

기고일: 2026.03.12

Similar Posts

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다