rcpputils 源码详细分析

rcpputils 源码详细分析

工作区路径:/home/cp/work2/ros2Learn/ros2_humble/src/ros2/rcpputils
版本:2.4.6(Humble),单包仓库,构建类型 ament_cmake,语言 C++14,许可证 Apache 2.0 + BSD-3-Clause(部分文件)。

rcpputils 是 ROS 2 C++ 工具库,提供跨平台文件系统、动态库加载、字符串处理、断言/异常、RAII scope guard、线程安全注解等。被 rclcpprclpy(C++ 扩展)、rosbag2rviz2class_loader 等广泛依赖。设计上 在 rcutils C API 之上提供 C++ 友好封装(异常、std::string、模板),同时补充 rcutils 未覆盖的 C++ 专属能力(std::chrono、type traits、std::filesystem 替代)。


1. 总体认识

1.1 核心职责

类别 模块 说明
RAII / 资源 scope_exit.hpp 作用域退出时自动执行清理
动态库 shared_library.hpp 封装 rcutils_shared_library,dlopen/LoadLibrary
文件系统 filesystem_helper.hpp std::filesystem 替代(跨平台 path 操作)
库查找 find_library.hpp LD_LIBRARY_PATH 等环境变量中搜索 .so
字符串 split/join/find_and_replace.hpp 分割、拼接、替换
环境 / 进程 env.hppprocess.hpp 环境变量、可执行文件名
断言 asserts.hpp require/check/assert 三层次异常
时间 time.hpp std::chrono → 纳秒安全转换
类型 traits pointer_traits.hpp 智能指针 type traits
字节序 endian.hpp C++20 std::endian 替代
线程注解 thread_safety_annotations.hpp Clang Thread Safety Analysis 宏
符号导出 visibility_control.hpp RCPPUTILS_PUBLIC
数学 rcppmath/clamp.hpprolling_mean_accumulator.hpp 钳制、滑动均值

1.2 在 ROS 2 栈中的位置

C++ 上层rcpputilsC 工具层rclcpprosbag2rviz2rclpy C++ 扩展rcpputils / rcppmathrcutilsOS / libc
对比 rcutils rcpputils
语言 C C++
日志/错误/分配器 核心实现 不重复,依赖 rcutils
文件系统 rcutils/filesystem.h(C) fs::path 等 C++ 风格 API
动态库 rcutils/shared_library.h SharedLibrary 类 + 异常
环境变量 rcutils/env.h get_env_var() 返回 std::string
适用层 rcl、rmw、rosidl C 运行时 rclcpp 及 C++ 工具/应用

分工原则:需要 C ABI 稳定性的放 rcutils;C++ 便利性与模板工具放 rcpputils。两者不应重复实现同一逻辑——rcpputils 的 .cpp 实现大多委托 rcutils。


2. 仓库结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
rcpputils/
├── include/
│ ├── rcpputils/ # 主命名空间(17 个头文件 + tl_expected/)
│ │ ├── filesystem_helper.hpp # 359 行,最大公开头
│ │ ├── thread_safety_annotations.hpp
│ │ ├── scope_exit.hpp
│ │ ├── shared_library.hpp
│ │ ├── split.hpp / join.hpp / find_and_replace.hpp
│ │ ├── asserts.hpp / env.hpp / find_library.hpp
│ │ ├── pointer_traits.hpp / time.hpp / endian.hpp
│ │ ├── process.hpp / visibility_control.hpp
│ │ ├── get_env.hpp # 已废弃,转发 env.hpp
│ │ └── tl_expected/expected.hpp # vendored(~2300 行,CC0)
│ └── rcppmath/ # 数学工具命名空间
│ ├── clamp.hpp
│ └── rolling_mean_accumulator.hpp
├── src/ # 5 个编译单元(~781 行)
│ ├── filesystem_helper.cpp # 494 行
│ ├── shared_library.cpp # 114 行
│ ├── find_library.cpp # 86 行
│ ├── env.cpp # 61 行
│ └── asserts.cpp # 38 行
├── test/ # 16 个 gtest
├── docs/FEATURES.md
├── CMakeLists.txt
└── package.xml

2.1 编译 vs 头文件-only

类型 文件 说明
编译进 librcpputils.so 5 个 .cpp 需链接 -lrcpputils
头文件-only scope_exitsplitjoinclamptimepointer_traitsendianprocess #include 即可
vendored 头 tl_expected/expected.hpp C++17 std::expected 替代,lint 排除

CMake 导出:ament_export_libraries(rcpputils)ament_export_dependencies(rcutils)


3. 依赖关系

1
2
rcpputils
└── rcutils # 唯一运行时依赖

构建工具:ament_cmakeament_cmake_rosament_cmake_gen_version_h

C++ 标准:C++14(注释说明待 CXX20 可用后移除 tl_expected)。


4. 核心模块详解

4.1 scope_exit — RAII 清理

ROS 2 C++ 代码中最常用的 rcpputils 特性之一。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template<typename CallableT>
struct scope_exit final
{
~scope_exit()
{
if (!cancelled_) {
callable_();
}
}
void cancel() { cancelled_ = true; }
...
};

#define RCPPUTILS_SCOPE_EXIT(code) \
auto RCUTILS_JOIN(scope_exit_, __LINE__) = rcpputils::make_scope_exit([&]() {code;})
API 用途
make_scope_exit(fn) 创建 scope guard
cancel() 资源已手动释放时取消自动清理
RCPPUTILS_SCOPE_EXIT(...) 宏简写(rclcpp executor、rosbag2 测试广泛使用)

典型场景:测试里确保进程/线程在退出时 join,或临时修改状态后恢复。

4.2 SharedLibrary — 动态库加载

C++ 封装 rcutils_shared_library_t

1
2
3
4
5
6
7
8
9
10
SharedLibrary::SharedLibrary(const std::string & library_path)
{
lib = rcutils_get_zero_initialized_shared_library();
rcutils_ret_t ret = rcutils_load_shared_library(
&lib, library_path.c_str(), rcutils_get_default_allocator());
if (ret != RCUTILS_RET_OK) {
...
throw std::runtime_error{rcutils_error_str};
}
}
方法 底层
构造 / 析构 rcutils_load/unload_shared_library
get_symbol() rcutils_get_symbol
has_symbol() rcutils_has_symbol
get_platform_library_name() rcutils_get_platform_library_name

下游关键用法rclcpp/typesupport_helpers.cpp 运行时加载 rosidl_typesupport_cpp 共享库以解析消息 typesupport:

1
2
3
const std::string library_path = rcpputils::path_for_library(
package_prefix + dynamic_library_folder,
package_name + "__" + typesupport_identifier);

4.3 find_library — 库路径搜索

1
2
3
4
5
6
7
8
9
10
11
12
std::string find_library_path(const std::string & library_name)
{
std::string search_path = get_env_var(kPathVar); // LD_LIBRARY_PATH / PATH / DYLD_...
std::vector<std::string> search_paths = rcpputils::split(search_path, kPathSeparator);
std::string filename = filename_for_library(library_name); // libfoo.so
for (const auto & search_path : search_paths) {
if (rcutils_is_file((search_path + "/" + filename).c_str())) {
return path;
}
}
return "";
}
函数 作用
find_library_path(name) 在 OS 库搜索路径中查找
path_for_library(dir, name) 在指定目录查找
filename_for_library(name) 生成平台库文件名(lib*.so / .dll / .dylib

4.4 filesystem_helper — 跨平台文件系统

源自 pluginlibfilesystem_helper,在 ROS 2 全平台尚未统一支持 std::filesystem 时提供替代。

命名空间rcpputils::fs

类型 / 函数 能力
fs::path 路径拼接 /exists()is_directory()parent_path()extension()
temp_directory_path() 临时目录(Windows: GetTempPathA;Unix: $TMPDIR/tmp
create_temp_directory(base, parent) 唯一临时目录(mkdtemp 风格)
create_directories(p) 递归创建目录
remove / remove_all 删除文件或目录树
current_path() 当前工作目录
copy / rename 文件复制与重命名

实现(494 行)调用 rcutils 与平台 API(statmkdirremove 等)。

下游:rosbag2 测试临时目录、rviz 配置文件路径、rclcpp 测试资源路径等。

4.5 字符串工具

头文件 函数 说明
split.hpp split(input, delim, skip_empty) 返回 vector<string> 或写入迭代器
join.hpp join(container, delim) 容器元素拼接
find_and_replace.hpp find_and_replace(str, from, to) 全局替换

下游示例

  • rclcpp_components/component_manager.cpp — 解析 ament index 组件清单(split by \n / ;
  • rosbag2_transport/topic_filter.cpp — topic 名 token 化
  • rclpy/node.cpp — 参数名 wildcard 正则构造(find_and_replace

4.6 env / process

env.cpp — 封装 rcutils 环境 API:

1
2
3
4
5
6
7
8
9
10
11
std::string get_env_var(const char * env_var)
{
const char * err = rcutils_get_env(env_var, &value);
if (err) throw std::runtime_error(err);
return value ? value : "";
}

bool set_env_var(const char * env_var, const char * env_value)
{
if (!rcutils_set_env(env_var, env_value)) { ... throw ... }
}

process.hpp — 头文件 inline 函数,调用 rcutils_get_executable_name() 返回 std::string

4.7 asserts — 三层次校验

函数 异常类型 行为
require_true(cond, msg) std::invalid_argument 校验输入参数
check_true(cond, msg) IllegalStateException 校验内部状态
assert_true(cond, msg) AssertionException 校验结果Release 下为 no-opNDEBUG

下游rclcpp/serialization.cpp 在序列化前 check_true 指针非空。

4.8 time.hpp — chrono 转换

1
2
3
4
5
6
7
8
template<typename DurationRepT, typename DurationT>
std::chrono::nanoseconds convert_to_nanoseconds(
const std::chrono::duration<DurationRepT, DurationT> & time)
{
if (time > ns_max_as_double) throw std::invalid_argument{...};
if (time < ns_min_as_double) throw std::invalid_argument{...};
return std::chrono::duration_cast<std::chrono::nanoseconds>(time);
}

防止 duration_cast 静默溢出,供 Executor 超时等场景使用。

4.9 pointer_traits — 智能指针 traits

扩展标准库:

  • rcpputils::is_pointer<T> — 识别 raw / shared_ptr / unique_ptr
  • rcpputils::remove_pointer<T> — 从任意指针类型提取 pointee

用于模板 API 同时接受 raw 与 smart pointer。

4.10 endian.hpp

  • C++17 以上:直接使用 std::endian
  • 否则:自定义 enum class endian { little, big, native }(Linux 用 <endian.h>

供序列化/网络字节序相关代码使用。

4.11 thread_safety_annotations.hpp

Clang Thread Safety Analysis 宏(GUARDED_BYLOCKS_EXCLUDED 等)。非 Clang 编译器下展开为空,跨平台安全。

测试构建时对 Clang 启用 -Wthread-safety -Werror

4.12 visibility_control.hpp

定义 RCPPUTILS_PUBLIC / RCPPUTILS_LOCAL / RCPPUTILS_BUILDING_DLL,控制 Windows DLL 导出与 Unix 默认 visibility。


5. rcppmath 命名空间

rcpputils 同包分发,独立命名空间 rcppmath

5.1 clamp.hpp

1
2
rcppmath::clamp(value, low, high);
rcppmath::clamp(value, low, high, Compare); // 自定义比较

C++14 时代尚未有标准 std::clamp(C++17)时的替代;行为与标准库一致。

5.2 RollingMeanAccumulator

滑动窗口均值累加器(替代 boost rolling mean,避免 boost 依赖):

1
2
void accumulate(T val) { ... O(1) 更新 sum_ ... }
T getRollingMean() const { return sum_ / valid_data_count; }

环形缓冲区 + 运行和,窗口满后 O(1) 更新。


6. tl_expected(vendored)

include/rcpputils/tl_expected/expected.hppTartanLlama/tl::expected 的 vendored 拷贝(CC0 公共领域)。

提供 C++17 风格的 expected<T, E>(类似 Rust Result),供尚未使用 C++23 std::expected 的代码使用。CMake lint 明确排除此文件。


7. 实现与 rcutils 委托关系

rcpputils C++ APIrcutils C APISharedLibraryget_env_varfs::pathfind_library_pathrcutils_shared_libraryrcutils_get/set_envrcutils_filesystem
rcpputils 委托 rcutils / OS
SharedLibrary rcutils_load/unload/get_symbol
get_env_var / set_env_var rcutils_get_env / rcutils_set_env
fs::* rcutils_is_file/is_directory + 平台 syscall
find_library_path rcutils_is_file + 环境变量路径
get_executable_name rcutils_get_executable_name

错误处理模式:rcutils 返回码 + 线程局部错误链 → rcpputils 提取 rcutils_get_error_string()throw std::runtime_error(SharedLibrary/env),或返回 bool/空字符串(find_library)。


8. 下游消费者

包 / 模块 使用的 rcpputils 能力
rclcpp SharedLibrarypath_for_library(typesupport 动态加载)、check_true(序列化)、RCPPUTILS_SCOPE_EXIT(executor/action)
rclcpp_components splitfs::path(解析组件资源)
rclpy(C++) find_and_replace(参数 wildcard)
rosbag2 fs::pathcreate_temp_directoryremove_allmake_scope_exitsplit
rviz2 fs::path
class_loader / pluginlib 历史上 filesystem/split 源码同源,现多直接使用 rcpputils

package.xml 声明依赖 rcpputils 的包:rclcpprclpyrosbag2 等。


9. 测试

test/ 下 16 个 gtest,与模块一一对应:

测试 覆盖
test_asserts debug/release 下 assert_true 行为差异
test_shared_library 加载 dummy .so、symbol 查找
test_filesystem_helper path 操作、临时目录
test_find_library LD_LIBRARY_PATH 搜索
test_env 空/正常环境变量
test_scope_exit cancel 与析构调用
test_thread_safety_annotations Clang 注解编译
test_clamp / test_accumulator rcppmath

10. 与 rcutils 文档对照

详见 rcutils 源码详细分析

需求场景 应使用
rcl/rmw C 代码 rcutils
rclcpp / C++ 工具 rcpputils(必要时仍可直接调 rcutils)
仅头文件 split/join #include rcpputils/split.hpp,无需链接
动态加载 typesupport #include rcpputils/shared_library.hpp,链接 rcpputils
日志 / 分配器 / C 错误链 rcutils(rcpputils 不提供)

11. 设计特点与演进

  1. 轻量:仅 5 个 .cpp,其余 header-only,降低链接开销
  2. 跨平台优先:filesystem、endian、库名前缀/后缀均处理 Windows/macOS/Linux 差异
  3. 渐进废弃get_env.hppenv.hppfs::path 待全平台 C++17 后或迁移至 std::filesystem
  4. Quality Level 1:见 QUALITY_DECLARATION.md,完整 CI lint + 测试
  5. 不重复 rcutils:新 C 级能力应先进 rcutils,C++ 包装再进 rcpputils

12. 推荐阅读顺序

  1. docs/FEATURES.md — 官方功能清单与示例
  2. scope_exit.hpp — 理解 ROS 2 C++ 中最常见的用法
  3. shared_library.cpp + find_library.cpp — 动态加载链(连接 rclcpp typesupport)
  4. filesystem_helper.hpp + .cpp — 最大模块,跨平台 path
  5. asserts.hpp — 三种断言语义
  6. split.hpp / join.hpp — 字符串工具模式
  7. rcppmath/* — 数学小工具
  8. 对照 rclcpp/typesupport_helpers.cpp — 真实集成场景
  9. 对照 rcutils 分析 — 理解 C/C++ 分层

13. 小结

rcpputils 是 ROS 2 C++ 生态的基础工具层,位于 rcutils 之上、rclcpp 之下:

  • 编译库(5 个源文件)提供 filesystem、shared_library、find_library、env、asserts
  • 头文件库提供 scope guard、字符串、chrono、traits、endian、线程注解
  • rcppmath 提供 clamp 与滑动均值
  • 核心模式:rcutils C API + C++ 异常/string + 跨平台 shim

日常阅读 rclcpp/rosbag2 代码时,常见 #include "rcpputils/..."RCPPUTILS_SCOPE_EXIT;排查动态 typesupport 加载问题时,应沿 path_for_librarySharedLibrary → rcutils 链分析。

文章互动

阅读 --

留言

0 条留言

正在加载留言…