auto和decltype

声明一个变量为 auto 类型时, 编译器将自动帮助你推导出合适的数据类型. 这个变量可以是 :

  • 声明后立即赋值的普通变量;
  • 函数的返回值;
  • 函数的形参 (C++14 起)

当你需要某个表达式的返回值类型而又不想实际执行它时用decltype

1
2
3
4
5
6
7
int  a=8, b=3;

//运行时需要实际执行a+b,哪怕编译时就能推导出类型
auto c = a+b;

//编译期类型推导不可以用auto c; 直接声明变量,必须同时初始化。
decltype(a+b) d;

decltype 的推导规则遵循如下几点 :

  • 若表达式是一个 不带括号的标记符表达式 或 类/结构体成员访问表达式, 那么推导的结果是所代表实体的类型;
  • 若表达式是一个函数调用(包括操作符重载), 那么推导的结果是函数的返回类型, 若返回值是基础类型则抛弃 const 限定符;
  • 若表达式是一个字符串字面量, 则推到为 const 左值引用;
  • 上述情况以外, 若表达式结果为左值则推导为左值引用, 否则推导为本类型;

拆分行为树

这里说的是官方教程中的拆分,实际开发中,如果行为树太大,应该根据程序逻辑拆分成几个xml文件,不同子逻辑加载不同的xml

拆分行为树

拆分行为树有两种方法,我倾向用不修改C++的方法

main_tree.xml如下

1
2
3
4
5
6
7
8
9
10
11
<root main_tree_to_execute = "MainTree">
<include path="./subtree_A.xml" />
<include path="./subtree_B.xml" />
<BehaviorTree ID="MainTree">
<Sequence>
<SaySomething message="starting MainTree" />
<SubTree ID="SubTreeA" />
<SubTree ID="SubTreeB" />
</Sequence>
</BehaviorTree>
<root>

1
2
3
4
5
6
7
8
9
10
11
<root>
<BehaviorTree ID="SubTreeA">
<SaySomething message="Executing Sub_A" />
</BehaviorTree>
</root>

<root>
<BehaviorTree ID="SubTreeB">
<SaySomething message="Executing Sub_B" />
</BehaviorTree>
</root>

include path可以用绝对路径,也可以用相对路径。

1
factory.createTreeFromFile("main_tree.xml")

拆分后,用Groot2打开main_tree.xml,可以正常编辑和跳转到子树。


函数Tree BT::BehaviorTreeFactory::createTreeFromFile写的不好,加载xml失败也不知道,最好返回类型是Bool,false代表加载失败。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Tree BehaviorTreeFactory::createTreeFromFile(const std::string& file_path,
Blackboard::Ptr blackboard)
{
if(!parser_->registeredBehaviorTrees().empty()) {
std::cout << "WARNING: You executed BehaviorTreeFactory::createTreeFromFile "
"after registerBehaviorTreeFrom[File/Text].\n"
"This is NOTm probably, what you want to do.\n"
"You should probably use BehaviorTreeFactory::createTree, instead"
<< std::endl;
}

XMLParser parser(*this);
parser.loadFromFile(file_path);
auto tree = parser.instantiateTree(blackboard);
tree.manifests = this->manifests();
return tree;
}

stack和queue

虽然stack和queue中也可以存放元素,但在STL中并没有将其划分在容器的行列,而是将其称为容器的适配器,这是因为stack和queue只是对其他容器的接口进行了包装,STL中stack和queue默认使用deque。

适配器其实是一种设计模式,该种模式是将一个类的接口转换成用户希望的另外一个接口。

deuqe平时很少作为单独的数据结构容器来使用,而是仅仅作为stack和queue等数据结构的适配容器。

stack是一种后进先出的特殊线性数据结构,因此只要具有push_back()pop_back()操作的线性结构,都可以作为stack的底层容器,比如vectorlist都可以;queue是先进先出的特殊线性数据结构,只要具有push_back和pop_front操作的线性结构,都可以作为queue的底层容器,比如list。但是STL中对stack和queue默认选择deque作为其底层容器,主要是因为:

  1. stack和queue不需要遍历(因此stack和queue没有迭代器),只需要在固定的一端或者两端进行操作。

  2. 在stack中元素增长时,deque比vector的效率高(扩容时不需要搬移大量数据);queue中的元素增长时,deque不仅效率高,而且内存使用率高。

deque支持stack和queue的全部操作,因此,只需要在stack和queue接口的实现中调用deque容器对应的接口,就可以完成对stack和deque的模拟实现。


CMake 常用宏和技巧

${CMAKE_CURRENT_LIST_DIR}${CMAKE_CURRENT_SOURCE_DIR} 一般都是当前CMakeLists.txt所在的目录。

file (GLOB_RECURSE SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/*.cpp) 把路径下的所有cpp文件名都加入变量SOURCE_FILES


优先队列 std::priority_queue

C++中的优先队列(priority queue)是一种特殊的队列,它允许在队列中添加元素时自动根据元素的优先级进行排序,
以便能够快速访问具有最高优先级的元素。优先队列可以用来解决很多算法问题,例如Dijkstra算法、Prim算法等。

底层实现是二叉堆,特别是二叉最小堆(对于最大优先级队列)或二叉最大堆(对于最小优先级队列)。堆是一种特殊的完全二叉树,其中任一节点的值都不大于或不小于其子节点的值。这种属性让堆非常适合快速访问最大或最小元素。

  • push() 基于优先级在适当的位置加入新的元素
  • top() 返回最高优先级的元素,但不删除该元素
  • pop() 删除最高优先级的元素
  • size() 返回队列中元素的个数
  • empty() 如果队列为空返回true,否则返回false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <queue>
#include <iostream>
using namespace std;

int main()
{
priority_queue<int> pq;
pq.push(3);
pq.push(1);
pq.push(4);
pq.push(1);
pq.push(5);

while (!pq.empty()) {
int top = pq.top();
pq.pop();
cout << top << " ";
}
// 如果要保存队列中的所有元素,可以将它们复制到另一个数据结构中,例如vector或数组
return 0;
}

优先队列不支持随机访问,只允许访问队头元素,不允许访问其余的数据


glog记录程序崩溃及扩展
abstract Welcome to my blog, enter password to read.
Read more
ROS2的package.xml和cmake

创建包,相关文件

创建C++包: ros2 pkg create --build-type ament_cmake --node-name node_name package_name

指明依赖项: ros2 pkg create --build-type ament_cmake --node-name node_name package_name --dependencies rclcpp std_msgs

创建python包: ros2 pkg create --build-type ament_python --node-name node_name package_name

如果在创建包时,忘了指定依赖项,需要手动修改package.xmlCMakeLists.txt

比如在package.xml<buildtool_depend> 之后添加

1
2
<depend>rclcpp</depend>
<depend>std_msgs</depend>

CMakeLists.txt添加
1
2
3
4
find_package(rclcpp  REQUIRED)
find_package(std_msgs REQUIRED)

ament_target_dependencies(my_node rclcpp std_msgs)

两个依赖项不能都放进find_package里面,否则报错

工作空间

  • build目录放的是中间文件.例如调用CMake时,每个包都会生成一个子文件夹.

  • install目录是放包的安装文件.默认情况下,每个包会被安装到一个独立的子目录.

  • log目录包含每次执行colcon的各种日志信息.

对比catkin,没有了devel目录


package.xml中包含该功能包的依赖信息,它可以帮助编译工具colcon确定多个功能包编译的顺序。

CMakeList.txt当中,必须有

1
2
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)

ament_cmake是cmake的增强版

find_package(rclcpp_lifecycle REQUIRED)用于生命周期管理,平时可以不添加

例如package名称为project(first_node),必须修改的部分如下

1
2
3
4
5
add_executable(test_node src/test.cpp)
ament_target_dependencies(test_node rclcpp std_msgs)
install(TARGETS test_node DESTINATION lib/install_node)

target_link_libraries(test_node behaviortree_cpp_v3)

  • add_executable不必解释

  • ament_target_dependencies是官方推荐的方式去添加依赖项。它将使依赖项的库、头文件和自身的依赖项被正常找到。如果不添加库名称,include头文件时会找不到

  • install是安装库的语句。它将在工作空间生成文件 install/first_node/lib/install_node/test_node

  • ament_package() 最后一句,不要修改。项目安装是通过ament_package()完成的,并且每个软件包必须恰好执行一次这个调用。ament_package()会安装package.xml文件,用ament索引注册该软件包,并安装CMake的配置(和可能的目标)文件,以便其他软件包可以用find_package找到该软件包。由于ament_package()会从CMakeLists.txt中收集大量信息,因此它应该是CMakeLists.txt中的最后一个调用。

可选项

ament_export_dependencies(${dependencies})

这句会将依赖项导出到下游软件包。这样该库使用者也就不必为那些依赖项调用find_package了。


行为树安装和配置

安装

安装依赖项,然后去github的release里下载最新的版本编译安装。

1
2
3
4
5
sudo apt-get sqlite3
sudo apt-get install libzmq3-dev libboost-dev
sudo apt-get install libboost-coroutine-dev # 需要用到协程

sudo apt-get install qtbase5-dev libqt5svg5-dev libzmq3-dev libdw-dev

普通的cmake设置如下

1
2
3
4
5
6
7
8
9
10
cmake_minimum_required(VERSION 3.10.2)

project(simple_bt)

set(CMAKE_CXX_SaTANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(behaviortree_cpp)

add_executable(${PROJECT_NAME} "simple_bt.cpp")
target_link_libraries(${PROJECT_NAME} BT::behaviortree_cpp)

如果是通过sudo apt-get install ros-humble-behavior-cpp安装的,只能在ROS2环境的设置,因为so文件依赖 ament_index_cpp:

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
27
28
29
30
31
32
33
34
cmake_minimum_required(VERSION 3.8)
project(test_node)

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

set(CMAKE_CXX_STANDARD 17)

add_compile_options(-Wextra -Wpedantic -Wno-unused-parameter -g)

# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
find_package(ament_index_cpp)
find_package(behaviortree_cpp)

INCLUDE_DIRECTORIES(/home/user/catkin_ws/src/test_node/include)

add_executable(test_node src/test.cpp)

ament_target_dependencies(test_node
rclcpp
behaviortree_cpp
${BTCPP_LIBRARY}
)

install(TARGETS
test_node
DESTINATION lib/${PROJECT_NAME}
)

ament_package()

package.xml如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>test_node</name>
<version>0.0.0</version>
<description>TODO: Package description</description>
<maintainer email="user@todo.todo">zzp</maintainer>
<license>TODO: License declaration</license>

<buildtool_depend>ament_cmake</buildtool_depend>

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

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

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

ROS2行为树动态库默认安装在/opt/ros/galactic/lib/libbehaviortree_cpp_v3.so,头文件在/opt/ros/foxy/include/behaviortree_cpp_v3


clang-format的使用

统一的代码规范对于整个团队来说十分重要,通过git/svn在提交前进行统一的 ClangFormat 格式化,可以有效避免由于人工操作带来的代码格式问题。

1
2
3
4
5
6
7
// 以 LLVM 代码风格格式化main.cpp, 结果输出到 stdout
clang-format -style=LLVM main.cpp
// 以 LLVM 风格格式化 main.cpp, 结果直接写到 main.cpp
clang-format -style=LLVM -i main.cpp

// 以 Google 风格格式化 main.cpp, 结果直接写到 main.cpp
clang-format -style=google -i main.cpp

除了LLVMGoogle外,还有 Chromium, Mozilla, WebKit

  • 批量格式化代码

find . -path '*/src/*.cpp' -o -path '*/include/*.h' ! -name 'sigslot.h' | xargs clang-format -style=file -i

在使用时,出现一个奇怪的bug,clang-format把我修改的文件的所有者都改成了root,再修改时还得先把用户改回来,极其不方便,不过我是在VMware里遇到的bug

参考: clang-format的介绍和使用


colcon编译

安装 colcon : sudo apt install python3-colcon-common-extensions。 ROS2的build没有了ROS1中的devel概念

与ROS1不同,如果一个包不放在src目录,而放在工作空间目录,colcon也会编译它。

  • colcon build     编译所有包

  • colcon build —packages-select pkg     只编译一个包

colcon build --packages-select 指令并不会编译该包的依赖,往往会报错。可以用下面这条指令进行包和其依赖编译

1
colcon build --packages-up-to   package_name
  • colcon build —cmake-args -DCMAKE_BUILD_TYPE=Release    

不编译测试单元

colcon test --packages-select YOUR_PKG_NAME --cmake-args -DBUILD_TESTING = 0

运行编译的包的测试

colcon test

  • 允许通过更改src下的部分文件改变install,这样每次修改Python脚本时不必重新 build

colcon build --symlink-install

  • colcon build —symlink-install pkg   

  • colcon build —symlink-install —packages-ignore pkg    

减小colcon的CPU占用

指定并行的线程数

1
colcon build --symlink-install --parallel-workers 1

不使用并行编译

1
colcon build --symlink-install  --executor sequential

两种编译不要混合用,否则每次都会重新编译

显示CMake中的MESSAGE

colcon build —event-handlers console_direct+ —packages-select test_node

console_direct 换成 console_cohesion 也可以

如果不想编译特定的包,在该包目录里面创建一个名为COLCON_IGNORE文件,这样子这个包就不会被索引到了

问题

  1. 编译时报错 CMake Error at /opt/ros/jazzy/share/rosidl_typesupport_c/cmake/get_used_typesupports.cmake:35 (message):
    No ‘rosidl_typesupport_c’ found

执行source后再编译

  1. 截图 2025-07-16 09-49-13.png

一开始我以为是/opt里的安装有问题,后来发现是ament_target_dependencies里没有添加nav_msgs,导致include头文件时会找不到

  1. failed to create symbolic link ‘/home/xxx/learning_interfaces2’ because existing path cannot be removed: Is a directory

把对应的目录删掉

  1. CMake Warning: Manually-specified variables were not used by the project: CATKIN_INSTALL_INTO_PREFIX_ROOT

在相应的包里加入

1
2
3
<export>                               
<build_type>ament_cmake</build_type>
</export>

  1. 如果需要在包的package.xml中添加依赖关系。比如sample包依赖common包,那么就在sample包的package.xml中添加
1
<depend>common</depend>

    告诉编译器,编译B包的时候依赖A包,A包需要先编译出来。

  1. 没有那个文件或目录
1
2
3
4
Starting >>> test_node
--- stderr: test_node
cc1plus: fatal error: /home/user/catkin_ws/src/test_node/src/Action.cpp: 没有那个文件或目录
compilation terminated.

删掉build目录中的test_node文件夹

  1. 编译自定义的msg包时,报错 找不到rosidl_typesupport_c

一般都安装了相应的库:

1
sudo apt install ros-jazzy-rosidl-typesupport-c

source /opt/ros/jazzy/setup.bash 即可

  1. CMake Warning: Manually-specified variables were not used by the project: CATKIN_INSTALL_INTO_PREFIX_ROOT

相应的包里明明没有用CATKIN相关的东西,不知是哪里报警,只好在CMake里加一句 unset( CATKIN_INSTALL_INTO_PREFIX_ROOT )

  1. 编译时找不到 ament_cmake

source /opt/ros/jazzy/setup.bash

  1. 报警: WARNING:colcon.colcon_ros.prefix_path.catkin:The path ‘/home/user/catkin_ws/install/nav2_map_server’ in the environment variable CMAKE_PREFIX_PATH doesn’t exist

即使删除了install, build, log也无法解决,要想彻底解决只能重建一个工作空间。或者用临时方法,但新终端又会失效

1
2
unset AMENT_PREFIX_PATH
unset CMAKE_PREFIX_PATH

  1. 包的依赖问题

在路径 /build/my_package/ament_cmake_core 可以看到编译一个包后生成的cmake文件,比如my_packageConfig.cmakemy_packageConfig-version.cmake

如果包A需要包B,编译时报错找不到BConfig.cmake,在工作空间执行 source