본문으로 바로가기

C++ ROS 2 패키지 생성하기

category 강좌/ROS2 2026. 5. 24. 03:19

1. 개요

이번 실습에서는 ROS 2 Humble 환경에서 C++ 패키지를 생성하고, Publisher 노드와 Subscriber 노드를 작성한 뒤 빌드하고 실행하는 과정을 진행합니다.

이번 실습에서 사용할 패키지 이름은 다음과 같습니다.

 
my_cpp_package
 

 

실습 목표는 다음과 같습니다.

작업공간 생성
→ C++ 패키지 생성
→ Publisher 노드 작성
→ Subscriber 노드 작성
→ CMakeLists.txt 수정
→ colcon build
→ 실행 확인
 

 

 

2. 실습 환경

이 글은 다음 환경을 기준으로 합니다.

OS: Ubuntu 22.04
ROS 2: Humble Hawksbill
언어: C++
빌드 타입: ament_cmake
빌드 도구: colcon
 

 

ROS 2 Humble 환경을 불러옵니다.

 
source /opt/ros/humble/setup.bash
 

 

 
3. C++ ROS 2 패키지 생성

 

src 폴더로 이동합니다.

 
cd ~/ros2_study/src
 

 

C++ 패키지를 생성합니다.

 
ros2 pkg create my_cpp_package --build-type ament_cmake --dependencies rclcpp std_msgs
 

 

생성 후 구조를 확인합니다.

 
tree my_cpp_package
 

 

 

예상 구조는 다음과 같습니다.

my_cpp_package/
├── CMakeLists.txt
├── include/
│   └── my_cpp_package/
├── package.xml
└── src/
 

 

 

 

 

4. package.xml 확인

src 폴더로 이용하여 code를 실행하고 package.xml을 불러옵니다.

 

package.xml은 패키지 정보와 의존성을 관리하는 파일입니다.

생성된 package.xml에서 다음 내용이 있는지 확인합니다.

 
<buildtool_depend>ament_cmake</buildtool_depend>

<depend>rclcpp</depend>
<depend>std_msgs</depend>
 

 

그리고 export 태그 안에 다음 내용이 들어가는 것이 좋습니다.

 
<export>
  <build_type>ament_cmake</build_type>
</export>
 

 

C++ ROS 2 패키지에서 <build_type>ament_cmake</build_type>가 빠지면 패키지 인식이나 빌드 과정에서 문제가 생길 수 있습니다.

 

최소 형태는 다음과 비슷합니다.

 
<?xml version="1.0"?>
<package format="3">
  <name>my_cpp_package</name>
  <version>0.0.0</version>
  <description>ROS 2 Humble C++ practice package for mobile robot examples</description>
  <maintainer email="user@example.com">user</maintainer>
  <license>Apache-2.0</license>

  <buildtool_depend>ament_cmake</buildtool_depend>

  <depend>rclcpp</depend>
  <depend>std_msgs</depend>

  <test_depend>ament_lint_auto</test_depend>
  <test_depend>ament_lint_common</test_depend>

  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>
 

 

 

 

 

5. CMakeLists.txt 기본 구조 확인

CMakeLists.txt는 C++ 파일을 어떻게 빌드하고 설치할지 정하는 파일입니다.

처음 생성된 파일은 대략 다음 구조를 가집니다.

 
cmake_minimum_required(VERSION 3.8)
project(my_cpp_package)

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)

ament_package()
 

 

여기에서 나중에 실행 파일 등록 내용을 추가합니다.

 

 

 

 

6. Publisher 노드 만들기

패키지 폴더로 이동합니다.

 
cd ~/ros2_study/src/my_cpp_package
 

 

Publisher 소스 파일을 만듭니다.

 
touch src/simple_publisher.cpp
 

 

 

 

파일을 엽니다.

 
code .
 

 

아래 코드를 작성합니다.

 
#include <chrono>
#include <functional>
#include <memory>
#include <string>

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

using namespace std::chrono_literals;

class SimplePublisher : public rclcpp::Node
{
public:
  SimplePublisher()
  : Node("simple_publisher"), count_(0)
  {
    publisher_ = this->create_publisher<std_msgs::msg::String>("robot_status", 10);

    timer_ = this->create_wall_timer(
      1s,
      std::bind(&SimplePublisher::timer_callback, this)
    );
  }

private:
  void timer_callback()
  {
    auto message = std_msgs::msg::String();
    message.data = "Mobile robot is alive: " + std::to_string(count_++);

    RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());

    publisher_->publish(message);
  }

  rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
  rclcpp::TimerBase::SharedPtr timer_;
  size_t count_;
};

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<SimplePublisher>());
  rclcpp::shutdown();

  return 0;
}
 

 

 

 

 

7. Publisher 코드 분석

1) ROS 2와 메시지 헤더 포함

 
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
 

 

rclcpp는 ROS 2 C++ 클라이언트 라이브러리입니다.
std_msgs/msg/string.hpp는 문자열 메시지를 사용하기 위한 헤더입니다.

 

 

 

2) 노드 클래스 선언

 
class SimplePublisher : public rclcpp::Node
 

 

SimplePublisher 클래스는 ROS 2 노드입니다.
rclcpp::Node를 상속받아서 직접 만든 노드 클래스로 사용합니다.

 

 

 

3) 생성자

 
SimplePublisher()
: Node("simple_publisher"), count_(0)
 

 

노드 이름을 simple_publisher로 설정합니다.
count_는 발행 횟수를 세기 위한 변수이며 0부터 시작합니다.

 

 

 

4) Publisher 생성

 
publisher_ = this->create_publisher<std_msgs::msg::String>("robot_status", 10);
 

 

robot_status라는 토픽으로 문자열 메시지를 발행하는 Publisher를 만듭니다.

여기서 10은 QoS 큐 크기입니다.


구독자가 잠시 메시지를 처리하지 못할 때 최대 10개까지 메시지를 버퍼에 저장할 수 있다는 의미입니다.

 

 

 

5) Timer 생성

 
timer_ = this->create_wall_timer(
  1s,
  std::bind(&SimplePublisher::timer_callback, this)
);
 

 

1초마다 timer_callback() 함수를 실행하는 타이머입니다.

즉, 이 노드는 1초 주기로 메시지를 발행합니다.

 

더보기
 
using namespace std::chrono_literals;
 

1s, 500ms 같은 시간 표현을 쓸 수 있게 해줍니다.

 

 

 

6) 콜백 함수

 
void timer_callback()
{
  auto message = std_msgs::msg::String();
  message.data = "Mobile robot is alive: " + std::to_string(count_++);

  RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());

  publisher_->publish(message);
}
 

 

이 함수가 1초마다 실행됩니다.

 

동작 순서는 다음과 같습니다.

  1. 문자열 메시지 객체 생성
  2. "Mobile robot is alive: 숫자" 형태의 데이터 작성
  3. 터미널에 발행 로그 출력
  4. robot_status 토픽으로 메시지 발행

 

 

7) main 함수

 
rclcpp::init(argc, argv);
 

 

ROS 2 시스템을 초기화합니다.

 
rclcpp::spin(std::make_shared<SimplePublisher>());
 

 

더보기

 

 

std::make_shared<>는 C++에서 객체를 동적으로 생성하고, 그 객체를 std::shared_ptr로 관리하게 해주는 함수입니다.

ROS 2 코드에서는 보통 노드를 만들 때 많이 사용합니다.

 
std::make_shared<SimplePublisher>()
 

풀어서 쓰면 아래 코드와 비슷합니다.

 
std::shared_ptr<SimplePublisher> node =
  std::make_shared<SimplePublisher>();
 

즉, SimplePublisher 노드 객체를 생성하는 코드입니다.

 

 

SimplePublisher node;
 

이렇게 일반 객체로 만들 수도 있지만, ROS 2의 rclcpp::spin()은 보통 스마트 포인터 형태의 노드 객체를 받습니다.

 

그래서 아래처럼 사용합니다.

 
rclcpp::spin(std::make_shared<SimplePublisher>());
 

 

shared_ptr는 여러 곳에서 함께 사용할 수 있는 포인터입니다.

객체를 사용하는 곳이 남아 있으면 객체가 유지되고, 더 이상 사용하는 곳이 없으면 자동으로 메모리가 해제됩니다.

즉, 직접 delete를 하지 않아도 됩니다.

 

 

SimplePublisher 노드를 실행합니다.
spin()은 타이머 콜백이나 구독 콜백 같은 ROS 이벤트를 계속 처리합니다.

 

 
rclcpp::shutdown();
 

 

노드 실행이 끝나면 ROS 2를 종료합니다.

 

 

 

8. Subscriber 노드 만들기

Subscriber 소스 파일을 만듭니다.

 
touch src/simple_subscriber.cpp
 
 
 
 

code를 실행하여 파일을 엽니다.

 

아래 코드를 작성합니다.

 
#include <functional>
#include <memory>

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

class SimpleSubscriber : public rclcpp::Node
{
public:
  SimpleSubscriber()
  : Node("simple_subscriber")
  {
    subscription_ = this->create_subscription<std_msgs::msg::String>(
      "robot_status",
      10,
      std::bind(&SimpleSubscriber::topic_callback, this, std::placeholders::_1)
    );
  }

private:
  void topic_callback(const std_msgs::msg::String::SharedPtr msg) const
  {
    RCLCPP_INFO(this->get_logger(), "Received: '%s'", msg->data.c_str());
  }

  rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
};

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<SimpleSubscriber>());
  rclcpp::shutdown();

  return 0;
}
 

 

 

 

 

9. Subscriber 코드 분석

 

1) 노드 클래스 선언

 
class SimpleSubscriber : public rclcpp::Node
 

 

SimpleSubscriber 클래스는 ROS 2 노드입니다.

rclcpp::Node를 상속받아서 Subscriber 기능을 가진 노드를 만듭니다.

 

 

 

2) 생성자

 
SimpleSubscriber()
: Node("simple_subscriber")
 

 

노드 이름을 simple_subscriber로 설정합니다.

 

 

3) Subscriber 생성

 
subscription_ = this->create_subscription<std_msgs::msg::String>(
  "robot_status",
  10,
  std::bind(&SimpleSubscriber::topic_callback, this, std::placeholders::_1)
);
 

 

robot_status 토픽을 구독하는 Subscriber를 생성합니다.

 

각 인자의 의미는 다음과 같습니다.

                                코드                                                                            의미
std_msgs::msg::String 구독할 메시지 타입
"robot_status" 구독할 토픽 이름
10 QoS 큐 크기
topic_callback 메시지를 받았을 때 실행할 함수

 

 

4) 콜백 함수

 
void topic_callback(const std_msgs::msg::String::SharedPtr msg) const
{
  RCLCPP_INFO(this->get_logger(), "Received: '%s'", msg->data.c_str());
}
 

 

토픽으로 메시지가 들어오면 이 함수가 자동으로 실행됩니다.

 

msg->data에는 Publisher가 보낸 문자열 데이터가 들어 있습니다. 즉, 받은 메시지를 터미널에 출력하는 역할을 합니다.

 

 

 

5) main 함수

 
rclcpp::init(argc, argv);
 

 

ROS 2를 초기화합니다.

 
rclcpp::spin(std::make_shared<SimpleSubscriber>());
 

 

SimpleSubscriber 노드를 생성하고 계속 실행합니다.
spin()이 실행 중이어야 토픽 메시지를 받을 수 있습니다.

 
rclcpp::shutdown();
 

 

ROS 2를 종료합니다.

 

 

 

10. CMakeLists.txt 수정하기

이제 C++ 파일을 빌드 대상에 추가합니다.

 

아래처럼 수정합니다.

 
cmake_minimum_required(VERSION 3.8)
project(my_cpp_package)

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)

add_executable(simple_publisher src/simple_publisher.cpp)
ament_target_dependencies(simple_publisher rclcpp std_msgs)

add_executable(simple_subscriber src/simple_subscriber.cpp)
ament_target_dependencies(simple_subscriber rclcpp std_msgs)

install(TARGETS
  simple_publisher
  simple_subscriber
  DESTINATION lib/${PROJECT_NAME}
)

if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  ament_lint_auto_find_test_dependencies()
endif()

ament_package()
 

 

중요한 부분은 다음입니다.

 
add_executable(simple_publisher src/simple_publisher.cpp)
ament_target_dependencies(simple_publisher rclcpp std_msgs)

add_executable(simple_subscriber src/simple_subscriber.cpp)
ament_target_dependencies(simple_subscriber rclcpp std_msgs)
 

 

그리고 실행하려면 반드시 설치 대상에 추가해야 합니다.

 
install(TARGETS
  simple_publisher
  simple_subscriber
  DESTINATION lib/${PROJECT_NAME}
)
 

 

이 부분이 없으면 빌드는 되더라도 ros2 run에서 실행 파일을 찾지 못할 수 있습니다.

 

 

 

 

 

11. 패키지 빌드하기

작업공간 루트로 이동합니다.

 
cd ~/ros2_study
 

 

전체 빌드합니다.

 
colcon build
 

 

특정 패키지만 빌드하려면 다음처럼 합니다.

 
colcon build --packages-select my_cpp_package
 

 

빌드가 성공하면 다음 폴더들이 생성됩니다.

ros2_ws/
├── build/
├── install/
├── log/
└── src/
 

 

 

 

 

12. 환경 설정하기

빌드 후에는 반드시 install/setup.bash를 불러옵니다.

 
source install/setup.bash
 

 

절대 빼먹으면 안 됩니다. 이걸 하지 않으면 새로 만든 패키지를 현재 터미널에서 찾지 못합니다.

 

 

 

13. 노드 실행하기

터미널을 2개 엽니다.

 

터미널 1: Publisher 실행

 
source /opt/ros/humble/setup.bash
source ~/ros2_study/install/setup.bash
ros2 run my_cpp_package simple_publisher
 

 

출력 예시는 다음과 같습니다.

 

[INFO] [simple_publisher]: Publishing: 'Mobile robot is alive: 0'
[INFO] [simple_publisher]: Publishing: 'Mobile robot is alive: 1'
[INFO] [simple_publisher]: Publishing: 'Mobile robot is alive: 2'
 
 
 

터미널 2: Subscriber 실행

 
source /opt/ros/humble/setup.bash
source ~/ros2_study/install/setup.bash
ros2 run my_cpp_package simple_subscriber
 

 

출력 예시는 다음과 같습니다.

[INFO] [simple_subscriber]: Received: 'Mobile robot is alive: 0'
[INFO] [simple_subscriber]: Received: 'Mobile robot is alive: 1'
[INFO] [simple_subscriber]: Received: 'Mobile robot is alive: 2'
 

 

이렇게 나오면 정상입니다.

 

 

 

 

14. ROS 2 명령어로 확인하기

실행 중인 노드를 확인합니다.

 
ros2 node list
 

 

예상 출력:

/simple_publisher
/simple_subscriber
 

 

목록을 확인합니다.

 
ros2 topic list
 

 

예상 출력:

/parameter_events
/robot_status
/rosout
 
 

 

데이터를 직접 확인합니다.

 
ros2 topic echo /robot_status
 

 

예상 출력:

 
data: 'Mobile robot is alive: 0'
---
data: 'Mobile robot is alive: 1'
---
data: 'Mobile robot is alive: 2'
---
 

 

 

정보를 확인합니다.

 
ros2 topic info /robot_status
 

 

예상 출력:

Type: std_msgs/msg/String
Publisher count: 1
Subscription count: 1
 

 

 

 

 

 

15. 최종 패키지 구조

 

실습이 끝난 후 패키지 구조는 다음과 같습니다.

my_cpp_package/
├── CMakeLists.txt
├── include/
│   └── my_cpp_package/
├── package.xml
└── src/
    ├── simple_publisher.cpp
    └── simple_subscriber.cpp
 

 

 

작업공간 전체 구조는 다음과 같습니다.

ros2_ws/
├── build/
├── install/
├── log/
└── src/
    └── my_cpp_package/
        ├── CMakeLists.txt
        ├── include/
        │   └── my_cpp_package/
        ├── package.xml
        └── src/
            ├── simple_publisher.cpp
            └── simple_subscriber.cpp
 
 
 
 

16. 실습 과제

과제 1. 이름 변경하기

현재 이름은 다음입니다.

 
"robot_status"
 

 

이것을 다음 이름으로 바꿔보세요.

 
"mobile_robot_status"
 

 

수정해야 할 파일:

simple_publisher.cpp
simple_subscriber.cpp
 

 

빌드:

 
cd ~/ros2_ws
colcon build --packages-select my_cpp_package
source install/setup.bash
 

 

확인:

 
ros2 topic list
ros2 topic echo /mobile_robot_status
 

 

 

 

 

과제 2. 발행 주기 변경하기

 

현재 Publisher는 1초마다 메시지를 보냅니다.

 
1s
 

 

이것을 0.5초로 바꿔보세요.

 
500ms
 

코드 상단에 이미 다음이 있으므로 바로 사용할 수 있습니다.

 
using namespace std::chrono_literals;
 

 

 

 

과제 3. 새 노드 추가하기

 

새 파일을 만듭니다.

 
touch src/robot_status_publisher.cpp
 

 

새 Publisher 노드를 만들고, 실행 파일 이름을 다음으로 등록해보세요.

 

robot_status_publisher
 

 

CMakeLists.txt에 추가할 내용:

 
add_executable(robot_status_publisher src/robot_status_publisher.cpp)
ament_target_dependencies(robot_status_publisher rclcpp std_msgs)
 

 

그리고 install()에도 추가합니다.

 
install(TARGETS
  simple_publisher
  simple_subscriber
  robot_status_publisher
  DESTINATION lib/${PROJECT_NAME}
)
 

 

빌드:

 
cd ~/ros2_ws
colcon build --packages-select my_cpp_package
source install/setup.bash
 

 

실행:

 
ros2 run my_cpp_package robot_status_publisher
 

728x90
728x90