C++ ROS 2 Package Template
The C++ ROS 2 Package template generates high-performance, composable ROS 2 nodes using modern C++ standards and best practices.
Overview
This template creates production-ready ROS 2 nodes with:
- Modern C++: C++17+ features with RAII principles
- Component Architecture: Full
rclcpp_components
support - Cross-Platform: Windows, Linux, and macOS compatibility
- Lifecycle Management: Optional
rclcpp_lifecycle
integration - Testing: Integrated gtest framework
- Visibility Control: Proper symbol visibility management
Generated Structure
your_package/
├── CMakeLists.txt # Modern Cmake with ROS distro detection
├── package.xml # ROS 2 package manifest
├── README.md # Package documentation
├── CONTRIBUTING.md # Development guidelines
├── Agents.md # AI interaction guide
├── include/
│ └── your_package/
│ ├── your_package.hpp # Main node header
│ └── your_package_visibility_control.h # Visibility macros
├── src/
│ ├── your_package.cpp # Main node implementation
│ └── your_package_component.cpp # Component registration
├── launch/
│ └── your_package_node.launch.py # Launch configuration
├── resource/
│ └── your_package # Ament index resource
└── test/
└── test_your_package.cpp # Unit tests
Key Features
Component-Based Architecture
Generated nodes are fully composable:
// Component registration
#include "rclcpp_components/register_node_macro.hpp"
RCLCPP_COMPONENTS_REGISTER_NODE(your_package::YourPackageNode);
Visibility Control
Cross-platform symbol visibility management:
// Visibility control header
#ifndef YOUR_PACKAGE__VISIBILITY_CONTROL_H_
#define YOUR_PACKAGE__VISIBILITY_CONTROL_H_
// Platform-specific visibility macros
#endif
// Class declaration
class YOUR_PACKAGE_PUBLIC YourPackageNode : public rclcpp::Node {
// ...
};
Modern CMake
Automatic ROS distribution detection:
# Runtime ROS distro detection
set(ROS_DISTRO $ENV{ROS_DISTRO})
if(ROS_DISTRO STREQUAL "humble" OR ROS_DISTRO STREQUAL "galactic" OR ROS_DISTRO STREQUAL "foxy")
# Legacy ament_target_dependencies approach
ament_target_dependencies(your_package_component rclcpp rclcpp_components)
else()
# Modern target_link_libraries approach
target_link_libraries(your_package_component PUBLIC rclcpp::rclcpp)
endif()
Usage Examples
Basic Publisher Node
class PublisherNode : public rclcpp::Node {
public:
PublisherNode() : Node("publisher_node") {
publisher_ = create_publisher<std_msgs::msg::String>("topic", 10);
timer_ = create_wall_timer(
std::chrono::milliseconds(100),
std::bind(&PublisherNode::timer_callback, this));
}
private:
void timer_callback() {
auto message = std_msgs::msg::String();
message.data = "Hello, ROS 2!";
RCLCPP_INFO(get_logger(), "Publishing: '%s'", message.data.c_str());
publisher_->publish(message);
}
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
rclcpp::TimerBase::SharedPtr timer_;
};
Service Server
class ServiceNode : public rclcpp::Node {
public:
ServiceNode() : Node("service_node") {
service_ = create_service<example_interfaces::srv::AddTwoInts>(
"add_two_ints",
std::bind(&ServiceNode::handle_service, this, _1, _2, _3));
}
private:
void handle_service(
const std::shared_ptr<rmw_request_id_t> request_header,
const std::shared_ptr<example_interfaces::srv::AddTwoInts::Request> request,
const std::shared_ptr<example_interfaces::srv::AddTwoInts::Response> response) {
response->sum = request->a + request->b;
RCLCPP_INFO(get_logger(), "Incoming request: %ld + %ld = %ld",
request->a, request->b, response->sum);
}
rclcpp::Service<example_interfaces::srv::AddTwoInts>::SharedPtr service_;
};
Configuration Options
Lifecycle Support
Enable lifecycle management for nodes that need state transitions: - Configure: Parameter validation and resource allocation - Activate: Start publishing/subscribing - Deactivate: Pause operations - Cleanup: Release resources - Shutdown: Final cleanup
Communication Patterns
- Publishers: Data broadcasting
- Subscribers: Data reception with callbacks
- Services: Request-response communication
- Actions: Long-running task coordination
- Parameters: Dynamic configuration
Building and Testing
Build Process
# Standard colcon build
colcon build --packages-select your_package
# With tests
colcon build --packages-select your_package --cmake-args -DBUILD_TESTING=ON
Running Tests
# Run tests
colcon test --packages-select your_package
# View results
colcon test-result --verbose
Component Loading
# Load component at runtime
ros2 component load /ComponentManager your_package YourPackageNode
Best Practices
Performance Optimization
- Use appropriate QoS settings for your use case
- Minimize dynamic memory allocation in hot paths
- Use
std::chrono
for timing instead of deprecated methods - Leverage C++17 features like
constexpr
andif constexpr
Error Handling
- Use ROS 2 logging macros (
RCLCPP_*
) - Check return values from ROS 2 APIs
- Implement proper exception safety
- Provide meaningful error messages
Thread Safety
- ROS 2 nodes are generally single-threaded
- Use mutexes for shared data access
- Consider using
rclcpp::executors::MultiThreadedExecutor
for concurrent operations
Troubleshooting
Common Issues
Visibility Errors
Symptoms: Linker errors about missing symbols
Solution: Ensure YOUR_PACKAGE_BUILDING_LIBRARY
is defined in CMakeLists.txt
Component Not Found
Symptoms: ros2 component list
doesn't show your component
Solution: Check that RCLCPP_COMPONENTS_REGISTER_NODE
is called and library is installed
Test Discovery Fails
Symptoms: Tests not found by colcon test
Solution: Ensure test files contain proper TEST()
or TEST_F()
macros
Debug Tips
- Use
RCLCPP_DEBUG
for detailed logging - Enable ROS 2 debug logging:
ros2 run your_package your_node --ros-args --log-level debug
- Use
gdb
orlldb
for native debugging - Check component loading with verbose output
Advanced Usage
Custom Message Types
Add custom message dependencies to package.xml
and CMakeLists.txt
:
<!-- package.xml -->
<depend>your_custom_msgs</depend>
# CMakeLists.txt
find_package(your_custom_msgs REQUIRED)
ament_target_dependencies(your_package_component your_custom_msgs)
Parameter Management
// Declare parameters
declare_parameter("rate", 10.0);
declare_parameter("topic_name", "default_topic");
// Use parameters
double rate = get_parameter("rate").as_double();
std::string topic = get_parameter("topic_name").as_string();
Lifecycle Integration
class LifecycleNode : public rclcpp_lifecycle::LifecycleNode {
// Implement lifecycle callbacks
LifecycleNodeReturn on_configure(const rclcpp_lifecycle::State &) override;
LifecycleNodeReturn on_activate(const rclcpp_lifecycle::State &) override;
LifecycleNodeReturn on_deactivate(const rclcpp_lifecycle::State &) override;
LifecycleNodeReturn on_cleanup(const rclcpp_lifecycle::State &) override;
LifecycleNodeReturn on_shutdown(const rclcpp_lifecycle::State &) override;
};
Migration from ROS 1
If migrating from ROS 1 C++ nodes:
- Replace ros::init()
with rclcpp::init()
- Use rclcpp::Node
instead of ros::NodeHandle
- Replace ROS 1 message types with ROS 2 equivalents
- Update CMakeLists.txt to use ament_cmake
- Use modern C++ features and smart pointers
Contributing
To improve this template:
- Test with different ROS 2 distributions
- Verify cross-platform compatibility
- Add performance benchmarks
- Include more communication pattern examples
- Enhance error handling and logging
See the contributing guide for details on modifying templates.