ROS2 C++ 回调函数 保姆级教程

目录

摘要

一、回调函数到底是什么?

[1.1 普通函数是主动调用](#1.1 普通函数是主动调用)

[1.2 回调函数是被动调用](#1.2 回调函数是被动调用)

[1.3 ROS2 为什么大量使用回调函数?](#1.3 ROS2 为什么大量使用回调函数?)

[1.4 回调函数和 rclcpp::spin() 的关系](#1.4 回调函数和 rclcpp::spin() 的关系)

[二、Topic 订阅回调函数详解](#二、Topic 订阅回调函数详解)

[2.1 Topic 回调函数的作用](#2.1 Topic 回调函数的作用)

[2.2 订阅 /cmd_vel 的完整代码](#2.2 订阅 /cmd_vel 的完整代码)

[2.3 这段代码的执行流程](#2.3 这段代码的执行流程)

[2.4 SharedPtr msg 是什么意思?](#2.4 SharedPtr msg 是什么意思?)

[2.5 为什么是 msg->linear.x?](#2.5 为什么是 msg->linear.x?)

[三、Timer 定时器回调函数详解](#三、Timer 定时器回调函数详解)

[3.1 Timer 回调函数的作用](#3.1 Timer 回调函数的作用)

[3.2 定时发布 /cmd_vel 的完整代码](#3.2 定时发布 /cmd_vel 的完整代码)

[3.3 Timer 回调函数什么时候执行?](#3.3 Timer 回调函数什么时候执行?)

[3.4 Timer 回调为什么没有参数?](#3.4 Timer 回调为什么没有参数?)

[四、Service 和 Action 中的回调函数](#四、Service 和 Action 中的回调函数)

[4.1 Service 服务回调函数的作用](#4.1 Service 服务回调函数的作用)

[4.2 自定义服务接口示例](#4.2 自定义服务接口示例)

[4.3 Service 回调函数代码示例](#4.3 Service 回调函数代码示例)

[4.4 Service 回调函数什么时候触发?](#4.4 Service 回调函数什么时候触发?)

[4.5 Action 中的回调函数简单理解](#4.5 Action 中的回调函数简单理解)

[五、std::bind、this、_1、_2 和常见问题总结](#五、std::bind、this、_1、_2 和常见问题总结)

[5.1 std::bind 是什么?](#5.1 std::bind 是什么?)

[5.2 &LimoTopicSub::cmd_callback 是什么意思?](#5.2 &LimoTopicSub::cmd_callback 是什么意思?)

[5.3 this 是什么意思?](#5.3 this 是什么意思?)

[5.4 _1 和 _2 是什么意思?](#5.4 _1 和 _2 是什么意思?)

[5.5 Timer 为什么不需要 _1?](#5.5 Timer 为什么不需要 _1?)

[5.6 也可以使用 Lambda 表达式写回调](#5.6 也可以使用 Lambda 表达式写回调)

[5.7 常见错误总结](#5.7 常见错误总结)

[(1)忘记写 rclcpp::spin(node)](#(1)忘记写 rclcpp::spin(node))

[(2)Topic 回调参数类型写错](#(2)Topic 回调参数类型写错)

(3)忘记包含

[(4)指针访问成员时写成了 .](#(4)指针访问成员时写成了 .)

[(5)Service 回调少写 _2](#(5)Service 回调少写 _2)

六、总结


摘要

在 ROS2 C++ 开发中,回调函数****是一个非常核心的概念

无论是订阅话题、定时发布消息、处理服务请求,还是执行 Action 任务,都会大量用到回调函数。

很多初学者刚开始学习 ROS2 时,经常会看到下面这种代码:

cpp 复制代码
std::bind(&LimoTopicSub::cmd_callback, this, std::placeholders::_1)

或者:

cpp 复制代码
this->create_wall_timer(
    500ms,
    std::bind(&LimoTopicCmd::timer_callback, this)
);

看起来比较复杂,其实本质并不难。

一句话理解:

回调函数就是提前写好的函数,等某个事件发生时,由 ROS2 自动帮我们调用。

本篇文章就围绕 ROS2 C++ 中的回调函数展开,结合 Topic、Timer、Service、Action 几种常见场景,详细讲清楚:

  1. 回调函数是什么
  2. 什么时候会触发
  3. std::bind 是什么
  4. this 是什么
  5. std::placeholders::_1 又是什么意思

一、回调函数到底是什么?

1.1 普通函数是主动调用

先看一个普通函数:

cpp 复制代码
#include <iostream>

void say_hello()
{
    std::cout << "Hello ROS2" << std::endl;
}

int main()
{
    say_hello();
    return 0;
}

这段代码中,say_hello() 是我们在 main() 函数中主动调用的。

执行流程是:

复制代码
main 函数开始执行
        ↓
主动调用 say_hello()
        ↓
say_hello() 执行
        ↓
程序结束

也就是说:

普通函数一般是我们自己主动调用。


1.2 回调函数是被动调用

回调函数不一样。

回调函数通常不是我们自己直接调用,而是提前把函数交给 ROS2,当某个事件发生时,ROS2 自动帮我们调用这个函数。

例如订阅 /cmd_vel 话题:

cpp 复制代码
sub_ = this->create_subscription<geometry_msgs::msg::Twist>(
    "/cmd_vel",
    10,
    std::bind(&LimoTopicSub::cmd_callback, this, std::placeholders::_1)
);

这里的 cmd_callback() 并不是我们自己手动调用的。

它的触发条件是:

复制代码
/cmd_vel 话题收到新消息
        ↓
ROS2 自动调用 cmd_callback()
        ↓
回调函数解析收到的消息

所以可以这样理解:

  1. 普通函数:我主动调用函数
  2. 回调函数:事件发生后,系统自动调用函数

1.3 ROS2 为什么大量使用回调函数?

ROS2 是机器人通信框架。

机器人程序和普通程序不太一样。

1. 普通程序很多时候是从上往下执行一遍:

复制代码
第一步
第二步
第三步
程序结束

2. 但是机器人程序需要一直运行,并且随时响应各种事件。

例如:

复制代码
有没有新的速度指令?
有没有新的雷达数据?
有没有客户端发送服务请求?
有没有新的 Action 目标?
定时器时间到了没有?

这些事件什么时候发生是不固定的。

所以ROS2 采用了事件驱动的方式:

  1. 收到话题消息 → 触发 Topic 回调函数
  2. 定时器到时间 → 触发 Timer 回调函数
  3. 收到服务请求 → 触发 Service 回调函数
  4. 收到 Action 目标 → 触发 Action 回调函数

这就是 ROS2 中回调函数非常重要的原因。


1.4 回调函数和 rclcpp::spin() 的关系

很多初学者写完回调函数后,发现程序没有反应。

其中一个常见原因就是没有写:

cpp 复制代码
rclcpp::spin(node);

一个完整的 ROS2 节点主函数一般是这样:

cpp 复制代码
int main(int argc, char * argv[])
{
    rclcpp::init(argc, argv);

    auto node = std::make_shared<LimoTopicSub>();

    rclcpp::spin(node);

    rclcpp::shutdown();

    return 0;
}

其中:

复制代码
rclcpp::spin(node);

可以理解为:

让节点一直运行,并且不断检查有没有事件需要处理。

比如:

复制代码
有没有新的 Topic 消息?
有没有 Timer 到时间?
有没有 Service 请求?
有没有 Action 目标?

如果有事件发生,ROS2 就会自动调用对应的回调函数。

所以:

1. 没有 spin:
节点创建完可能就结束了,回调函数没有机会执行。

2. 有 spin:
节点持续运行,等待事件发生,然后触发回调函数。


二、Topic 订阅回调函数详解

2.1 Topic 回调函数的作用

Topic 订阅回调函数的作用是:

当订阅的话题收到新消息时,自动执行对应的处理逻辑。

例如小车控制中经常使用的话题:

复制代码
/cmd_vel

消息类型是:

复制代码
geometry_msgs/msg/Twist

这个话题通常用来发送小车速度指令。


2.2 订阅 /cmd_vel 的完整代码

下面是一个 ROS2 C++ 订阅者节点,用来订阅 /cmd_vel 话题。

cpp 复制代码
#include <memory>
#include <functional>

#include "rclcpp/rclcpp.hpp"
#include "geometry_msgs/msg/twist.hpp"

using std::placeholders::_1;

class LimoTopicSub : public rclcpp::Node
{
public:
    LimoTopicSub() : Node("limo_topic_sub")
    {
        cmd_sub_ = this->create_subscription<geometry_msgs::msg::Twist>(
            "/cmd_vel",
            10,
            std::bind(&LimoTopicSub::cmd_callback, this, _1)
        );

        RCLCPP_INFO(this->get_logger(), "limo_topic_sub node has started.");
    }

private:
    void cmd_callback(const geometry_msgs::msg::Twist::SharedPtr msg)
    {
        RCLCPP_INFO(this->get_logger(),
                    "收到速度指令:linear.x = %.2f, angular.z = %.2f",
                    msg->linear.x,
                    msg->angular.z);
    }

    rclcpp::Subscription<geometry_msgs::msg::Twist>::SharedPtr cmd_sub_;
};

int main(int argc, char * argv[])
{
    rclcpp::init(argc, argv);

    auto node = std::make_shared<LimoTopicSub>();

    rclcpp::spin(node);

    rclcpp::shutdown();

    return 0;
}

2.3 这段代码的执行流程

代码运行后,整体流程如下:

cpp 复制代码
程序启动
    ↓
创建 LimoTopicSub 节点
    ↓
执行构造函数 LimoTopicSub()
    ↓
创建 /cmd_vel 订阅者
    ↓
打印:limo_topic_sub node has started.
    ↓
进入 rclcpp::spin(node)
    ↓
等待 /cmd_vel 话题消息
    ↓
一旦收到 Twist 消息
    ↓
ROS2 自动调用 cmd_callback()
    ↓
打印 linear.x 和 angular.z

所以如果一直没有任何节点往 /cmd_vel 发布消息,终端通常只会看到:

复制代码
[INFO] [limo_topic_sub]: limo_topic_sub node has started.

然后程序不会结束,而是一直卡在这里等待:

复制代码
rclcpp::spin(node);

因此:

复制代码
cmd_callback()

不需要我们自己手动调用。

只要 /cmd_vel 收到新消息,ROS2 就会自动调用它。


2.4 SharedPtr msg 是什么意思?

回调函数中有这样一行:

cpp 复制代码
void cmd_callback(const geometry_msgs::msg::Twist::SharedPtr msg)

可以拆开理解:

复制代码
geometry_msgs::msg::Twist

表示消息类型是 Twist

复制代码
SharedPtr

表示智能指针。

复制代码
msg

表示变量名。

所以这一行整体意思是:

  1. 定义一个回调函数 cmd_callback
  2. 它接收一个 Twist 类型的消息指针
  3. 这个消息指针的名字叫 msg

其中 msg 只是变量名,可以修改。

例如:

cpp 复制代码
void cmd_callback(const geometry_msgs::msg::Twist::SharedPtr data)
{
    RCLCPP_INFO(this->get_logger(), "linear.x = %.2f", data->linear.x);
}

这样写也是可以的。

只不过ROS2 代码中习惯写成 msg,因为它一看就知道代表接收到的消息。


2.5 为什么是 msg->linear.x

因为 msg 是指针,所以访问里面的成员变量时要用:

复制代码
msg->linear.x
msg->angular.z

而不是:

复制代码
msg.linear.x
msg.angular.z

简单记:

复制代码
普通对象访问成员:用 .
指针访问成员:用 ->

所以:

复制代码
msg->linear.x

可以理解为:

复制代码
访问 msg 指向的 Twist 消息里面的 linear.x 字段

三、Timer 定时器回调函数详解

3.1 Timer 回调函数的作用

Timer 定时器回调函数用于周期性执行某个任务。

例如:

  1. 每 500ms 发布一次速度指令
  2. 每 1 秒打印一次状态
  3. 每 100ms 读取一次传感器数据
  4. 每隔一段时间检查一次任务状态

在 ROS2 小车控制中,经常会用 Timer 定时发布 /cmd_vel 速度指令。


3.2 定时发布 /cmd_vel 的完整代码

下面这个节点会每隔 500ms 执行一次 timer_callback(),并向 /cmd_vel 发布速度消息

cpp 复制代码
#include <memory>
#include <functional>
#include <chrono>

#include "rclcpp/rclcpp.hpp"
#include "geometry_msgs/msg/twist.hpp"

using namespace std::chrono_literals;

class LimoTopicCmd : public rclcpp::Node
{
public:
    LimoTopicCmd() : Node("limo_topic_cmd"), count_(0)
    {
        pub_vel_ = this->create_publisher<geometry_msgs::msg::Twist>(
            "/cmd_vel",
            10
        );

        timer_ = this->create_wall_timer(
            500ms,
            std::bind(&LimoTopicCmd::timer_callback, this)
        );

        RCLCPP_INFO(this->get_logger(), "limo_topic_cmd node has started.");
    }

private:
    void timer_callback()
    {
        geometry_msgs::msg::Twist vel_cmd;

        if (count_ < 4)
        {
            vel_cmd.linear.x = 0.1;
            vel_cmd.angular.z = 0.0;

            RCLCPP_INFO(this->get_logger(), "小车前进中...");
        }
        else
        {
            vel_cmd.linear.x = 0.0;
            vel_cmd.angular.z = 0.0;

            RCLCPP_INFO(this->get_logger(), "小车停止。");
        }

        pub_vel_->publish(vel_cmd);

        count_++;
    }

    rclcpp::Publisher<geometry_msgs::msg::Twist>::SharedPtr pub_vel_;
    rclcpp::TimerBase::SharedPtr timer_;

    int count_;
};

int main(int argc, char * argv[])
{
    rclcpp::init(argc, argv);

    auto node = std::make_shared<LimoTopicCmd>();

    rclcpp::spin(node);

    rclcpp::shutdown();

    return 0;
}

3.3 Timer 回调函数什么时候执行?

核心代码是:

cpp 复制代码
timer_ = this->create_wall_timer(
    500ms,
    std::bind(&LimoTopicCmd::timer_callback, this)
);

这段代码的意思是:

  1. 创建一个定时器
  2. 每隔 500ms
  3. 自动调用一次 timer_callback()

执行流程如下:

复制代码
启动 limo_topic_cmd 节点
        ↓
创建 /cmd_vel 发布者
        ↓
创建 500ms 定时器
        ↓
进入 rclcpp::spin(node)
        ↓
每隔 500ms 自动触发 timer_callback()
        ↓
发布一次 Twist 速度消息

所以 Timer 回调函数的特点是:

  1. 不需要外部发送消息
  2. 只要时间到了,就自动执行

3.4 Timer 回调为什么没有参数?

Topic 订阅回调一般有参数:

cpp 复制代码
void cmd_callback(const geometry_msgs::msg::Twist::SharedPtr msg)

因为Topic 回调需要接收外部传来的消息。

但是 Timer 回调一般没有参数:

复制代码
void timer_callback()

因为它只是时间到了自动执行,不需要 ROS2 额外传入消息。

所以可以这样区分:

  1. Topic 回调:收到消息后触发,通常有 msg 参数
  2. Timer 回调:定时时间到了触发,通常没有参数

四、Service 和 Action 中的回调函数

4.1 Service 服务回调函数的作用

Service 是一种请求 / 响应通信方式。

它的通信流程是:

复制代码
客户端发送请求
        ↓
服务端处理请求
        ↓
服务端返回响应

服务端中,负责处理请求的函数就是 Service 回调函数。

例如我们定义一个小车控制服务:

复制代码
客户端发送 x、y、z
服务端收到后发布 /cmd_vel
最后返回 success

4.2 自定义服务接口示例

假设自定义服务文件为:

复制代码
limo_msgs/srv/LimoSrv.srv

内容如下:

复制代码
float32 x
float32 y
float32 z
---
bool success

其中:

复制代码
--- 上面是请求 Request
--- 下面是响应 Response

也就是说:

客户端发送:

复制代码
x
y
z

服务端返回:

复制代码
success

4.3 Service 回调函数代码示例

cpp 复制代码
#include <memory>
#include <functional>

#include "rclcpp/rclcpp.hpp"
#include "geometry_msgs/msg/twist.hpp"
#include "limo_msgs/srv/limo_srv.hpp"

using std::placeholders::_1;
using std::placeholders::_2;

class LimoSrvServer : public rclcpp::Node
{
public:
    LimoSrvServer() : Node("limo_srv_server")
    {
        cmd_pub_ = this->create_publisher<geometry_msgs::msg::Twist>(
            "/cmd_vel",
            10
        );

        srv_ = this->create_service<limo_msgs::srv::LimoSrv>(
            "/limo_srv",
            std::bind(&LimoSrvServer::srv_callback, this, _1, _2)
        );

        RCLCPP_INFO(this->get_logger(), "limo_srv_server node has started.");
    }

private:
    void srv_callback(
        const std::shared_ptr<limo_msgs::srv::LimoSrv::Request> request,
        std::shared_ptr<limo_msgs::srv::LimoSrv::Response> response)
    {
        geometry_msgs::msg::Twist vel_cmd;

        vel_cmd.linear.x = request->x;
        vel_cmd.linear.y = request->y;
        vel_cmd.angular.z = request->z;

        cmd_pub_->publish(vel_cmd);

        response->success = true;

        RCLCPP_INFO(this->get_logger(),
                    "收到服务请求:x = %.2f, y = %.2f, z = %.2f",
                    request->x,
                    request->y,
                    request->z);
    }

    rclcpp::Publisher<geometry_msgs::msg::Twist>::SharedPtr cmd_pub_;
    rclcpp::Service<limo_msgs::srv::LimoSrv>::SharedPtr srv_;
};

int main(int argc, char * argv[])
{
    rclcpp::init(argc, argv);

    auto node = std::make_shared<LimoSrvServer>();

    rclcpp::spin(node);

    rclcpp::shutdown();

    return 0;
}

4.4 Service 回调函数什么时候触发?

这一行代码创建了服务端:

cpp 复制代码
srv_ = this->create_service<limo_msgs::srv::LimoSrv>(
    "/limo_srv",
    std::bind(&LimoSrvServer::srv_callback, this, _1, _2)
);

意思是:

  1. 创建一个 /limo_srv 服务
  2. 当客户端调用 /limo_srv 时
  3. 自动执行 srv_callback()

执行流程如下:

复制代码
服务端启动
        ↓
创建 /limo_srv 服务
        ↓
进入 rclcpp::spin(node)
        ↓
客户端发送请求
        ↓
ROS2 自动调用 srv_callback()
        ↓
服务端读取 request
        ↓
服务端填写 response
        ↓
结果返回给客户端

4.5 Action 中的回调函数简单理解

Action 适合处理长时间任务。

例如:

复制代码
导航到目标点
巡检一段路线
让小车持续运动一段时间
机械臂执行抓取任务

Action 和 Service 的区别是:

  1. Service:一次请求,一次响应
  2. Action:发送目标,持续反馈,最终返回结果

Action 服务端常见回调函数有:

  1. handle_goal:收到目标时触发
  2. handle_cancel:收到取消请求时触发
  3. handle_accepted:目标被接受后触发
  4. execute:真正执行任务的函数

Action 客户端常见回调函数有:

  1. goal_response_callback:服务端是否接受目标
  2. feedback_callback:接收执行过程反馈
  3. result_callback:接收最终执行结果

所以Action 中的回调更多,是因为 Action 需要处理完整的任务生命周期

可以简单理解为:

  1. Topic 回调:收到消息就触发
  2. Timer 回调:时间到了就触发
  3. Service 回调:客户端请求来了就触发
  4. Action 回调:目标、反馈、结果等不同阶段都会触发

五、std::bind、this、_1、_2 和常见问题总结

5.1 std::bind 是什么?

在 ROS2 C++ 中,经常会看到这种写法:

cpp 复制代码
std::bind(&LimoTopicSub::cmd_callback, this, _1)

它的作用是:

把类里面的成员函数绑定成 ROS2 可以调用的回调函数。

  1. 因为 cmd_callback() 是类 LimoTopicSub 里面的成员函数。
  2. 成员函数不能像普通函数一样直接传给 ROS2。

所以需要告诉 ROS2:

复制代码
这个回调函数属于哪个类?
用哪个对象来调用?
将来要接收几个参数?

std::bind 就是用来完成这件事的。


5.2 &LimoTopicSub::cmd_callback 是什么意思?

这一部分:

复制代码
&LimoTopicSub::cmd_callback

表示:

复制代码
取出 LimoTopicSub 类中的 cmd_callback 成员函数地址

可以理解为:

复制代码
我要把 LimoTopicSub 这个类里面的 cmd_callback 函数交给 ROS2

5.3 this 是什么意思?

这一部分:

复制代码
this

表示当前对象。

因为 cmd_callback() 是类里面的成员函数,它必须依赖某一个具体对象才能调用。

例如:

复制代码
std::bind(&LimoTopicSub::cmd_callback, this, _1)

可以理解为:

复制代码
使用当前这个 LimoTopicSub 节点对象
去调用它自己的 cmd_callback 函数

也就是说,this 指向当前节点对象。


5.4 _1_2 是什么意思?

_1 完整写法是:

复制代码
std::placeholders::_1

是一个占位符

意思是:

  1. 这里先占一个位置
  2. 等 ROS2 真正调用回调函数时
  3. 把第 1 个参数传进来

例如 Topic 回调函数:

复制代码
void cmd_callback(const geometry_msgs::msg::Twist::SharedPtr msg)

这个函数需要一个参数 msg

所以绑定时写:

复制代码
std::bind(&LimoTopicSub::cmd_callback, this, _1)

意思是:

  1. ROS2 收到消息后
  2. 把收到的消息作为第 1 个参数
  3. 传给 cmd_callback()

Service 回调函数一般有两个参数:

复制代码
request
response

所以绑定时写:

复制代码
std::bind(&LimoSrvServer::srv_callback, this, _1, _2)

其中:

复制代码
_1 对应 request
_2 对应 response

也就是:

复制代码
客户端请求来了
        ↓
ROS2 把请求数据放到 _1
        ↓
ROS2 把响应对象放到 _2
        ↓
调用 srv_callback(request, response)

5.5 Timer 为什么不需要 _1

Timer 回调函数一般没有参数:

复制代码
void timer_callback()

所以绑定时不需要 _1

复制代码
std::bind(&LimoTopicCmd::timer_callback, this)

因为 Timer 回调不是接收消息,也不是处理请求。

它只是:

复制代码
时间到了
        ↓
自动执行 timer_callback()

所以不需要 ROS2 传入额外参数。


5.6 也可以使用 Lambda 表达式写回调

除了 std::bind,ROS2 C++ 中也可以使用 Lambda 表达式。

Topic 回调可以写成:

cpp 复制代码
```cpp
cmd_sub_ = this->create_subscription<geometry_msgs::msg::Twist>(
    "/cmd_vel",
    10,
    [this](const geometry_msgs::msg::Twist::SharedPtr msg)
    {
        this->cmd_callback(msg);
    }
);
```

对比:

cpp 复制代码
cmd_sub_ = this->create_subscription<geometry_msgs::msg::Twist>(
    "/cmd_vel",
    10,
    std::bind(&LimoTopicSub::cmd_callback, this, _1)

Timer 回调可以写成:

cpp 复制代码
```cpp
timer_ = this->create_wall_timer(
    500ms,
    [this]()
    {
        this->timer_callback();
    }
);
```

对比:

cpp 复制代码
timer_ = this->create_wall_timer(
    500ms,
    std::bind(&LimoTopicCmd::timer_callback, this)
);

这两种写法本质上都可以。

对初学者来说:

复制代码
std::bind 写法:ROS2 官方示例中比较常见
Lambda 写法:看起来更直观,参数更清楚

实际项目中,两种写法都能用。


5.7 常见错误总结

(1)忘记写 rclcpp::spin(node)

如果没有写:

复制代码
rclcpp::spin(node);

节点可能创建完就结束,回调函数不会被触发。


(2)Topic 回调参数类型写错

订阅 /cmd_vel 时,消息类型是:

复制代码
geometry_msgs::msg::Twist

那么回调函数参数也要对应:

复制代码
void cmd_callback(const geometry_msgs::msg::Twist::SharedPtr msg)

如果类型不一致,就会编译报错。


(3)忘记包含 <functional>

如果使用了:

复制代码
std::bind
std::placeholders::_1

就需要包含头文件:

复制代码
#include <functional>

否则可能会出现 std::bind 未定义的问题。


(4)指针访问成员时写成了 .

错误写法:

复制代码
msg.linear.x

正确写法:

复制代码
msg->linear.x

因为 msg 是指针,所以要用 ->


(5)Service 回调少写 _2

Service 回调一般有两个参数:

复制代码
request
response

所以绑定时一般要写:

复制代码
std::bind(&LimoSrvServer::srv_callback, this, _1, _2)

如果只写 _1,参数数量对不上,也会出错。


六、总结

本篇文章主要讲解了ROS2 C++ 中非常重要的回调函数

回调函数可以简单理解为:

提前写好的函数,等某个事件发生时,由 ROS2 自动调用。

在 ROS2 中,常见的回调场景包括:

  1. Topic 回调:收到话题消息时触发
  2. Timer 回调:定时时间到了触发
  3. Service 回调:客户端发送请求时触发
  4. Action 回调:目标、反馈、结果等阶段触发

其中:

复制代码
std::bind

用于把类成员函数绑定成 ROS2 可以调用的回调函数。

复制代码
this

表示当前节点对象。

复制代码
_1

表示第一个参数。

复制代码
_2

表示第二个参数。

再简单总结一句:

  • Topic 收消息靠回调
  • Timer 定时执行靠回调
  • Service 处理请求靠回调
  • Action 管理任务过程也靠回调

理解了回调函数,后面学习 ROS2 的 Publisher、Subscriber、Service、Action,就会轻松很多。