欢迎光临
我们一直在努力

C++ 补充说明:ROS2 Subscriber 为什么常用智能指针接收消息?

在刚开始学习 ROS2 C++ 的 Publisher / Subscriber 时,我一直有一个疑问:

[this](std_msgs::msg::Int32::ConstSharedPtr msg)
{
receiveNumber(msg);
}

这里为什么突然出现了一个 ConstSharedPtr?

Publisher 明明发送的是消息,为什么到了 Subscriber 之后却变成了一个“智能指针”?

这个智能指针是不是直接指向 Topic,例如 /number?

理解清楚之后,我发现这里其实混合了三个完全不同的概念:

Topic、消息对象、智能指针。


问题 1:Publisher 发布的到底是什么?

例如 Publisher 中:

std_msgs::msg::Int32 msg;

msg.data = 5;

publisher_->publish(msg);

这里的:

msg

是一个真正的 Int32 消息对象。

可以简单理解为:

msg
┌──────────────┐
│ data = 5 │
└──────────────┘

然后:

publisher_->publish(msg);

表示通过 Publisher 发布这条消息。

需要特别注意:

Publisher 发布的并不是这个 C++ 对象本身的内存地址。

不是:

把 &msg 发给 Subscriber

因为 Publisher 和 Subscriber 可能属于两个不同进程,甚至可能运行在两台不同计算机上。

一个进程中的内存地址对另一个进程通常没有直接意义。

因此,从概念上理解:

Publisher中的消息对象
data = 5
↓
ROS2 / DDS通信
↓
传递消息内容
↓
Subscriber一侧得到对应消息


问题 2:那为什么 Subscriber 最后得到的是一个智能指针?

当消息到达 Subscriber 一侧以后,ROS2 已经获得了一条接收到的消息对象。

例如:

Subscriber侧

Int32消息对象
┌──────────────┐
│ data = 5 │
└──────────────┘

接下来 ROS2 要调用我们提前注册好的 callback。

如果每次调用 callback 都重新完整复制一次消息对象:

ROS2中的消息对象
↓
重新复制整个对象
↓
callback中的新对象

对于简单的 Int32 来说问题不大。

但如果以后收到的是:

Camera Image
PointCloud
LaserScan
大型机器人状态消息

消息可能非常大。

因此 ROS2 C++ 中很常见的一种方式是:

不再为了进入 callback 而额外完整复制一个消息对象,而是把指向已有消息对象的智能指针交给 callback。

于是:

std_msgs::msg::Int32::ConstSharedPtr msg

可以理解为:

msg
│
▼
Subscriber侧收到的Int32消息对象
┌──────────────┐
│ data = 5 │
└──────────────┘

所以:

msg->data

就是:

顺着msg这个智能指针
↓
找到Int32消息对象
↓
访问里面的data

最终得到:

5


问题 3:智能指针是不是指向 /number Topic?

不是。

这是最容易混淆的地方。

msg:

std_msgs::msg::Int32::ConstSharedPtr msg

指向的是:

某一条具体收到的 Int32 消息对象。

而 /number 是:

Topic 的名称。

Topic 是在创建 Subscription 时确定的:

subscription_ =
this->create_subscription<std_msgs::msg::Int32>(
"number",
10,
callback
);

这里实际上定义了三个重要信息:

std_msgs::msg::Int32
→ 我要接收什么类型

"number"
→ 我要监听哪个Topic

callback
→ 消息到了以后执行什么

因此更准确的关系是:

Subscription对象
│
├── 监听Topic:/number
│
├── 消息类型:Int32
│
└── 保存callback

而不是:

msg指针
↓
/number


问题 4:为什么 msg 知道自己指向的是 Int32?

因为它的类型已经写得非常明确:

std_msgs::msg::Int32::ConstSharedPtr msg

可以从右往左理解:

msg
→ 一个智能指针

ConstSharedPtr
→ 指向只读对象的shared_ptr类型

Int32
→ 被指向的对象是Int32消息

因此:

msg->data

编译器知道:

msg 指向的是 Int32 对象,因此这个对象具有 data 成员。


问题 5:为什么 Subscriber 能和 Publisher 匹配上?

不单单因为 msg 是 Int32。

真正重要的是 Publisher 和 Subscriber 在通信配置上能够对应。

例如 Publisher:

this->create_publisher<std_msgs::msg::Int32>(
"number",
10
);

Subscriber:

this->create_subscription<std_msgs::msg::Int32>(
"number",
10,
callback
);

两边共同满足:

Topic名称:
number

消息类型:
Int32

所以它们能够通过 ROS2 通信。

可以简单理解为:

Publisher:
我往 number 频道发送 Int32

Subscriber:
我监听 number 频道里的 Int32

于是可以匹配。


问题 6:Publisher 端的 msg 和 Subscriber 端的 msg 是同一个对象吗?

通常不要这样理解。

Publisher 中:

std_msgs::msg::Int32 msg;

这里的 msg 是 Publisher 一侧创建的对象。

Subscriber 中:

std_msgs::msg::Int32::ConstSharedPtr msg

这里的 msg 是 Subscriber callback 得到的智能指针。

变量名虽然都叫:

msg

但它们不是同一个变量。

概念上应该理解:

Publisher侧消息对象
data = 5
↓
ROS2通信系统
↓
Subscriber侧得到消息
data = 5
↑
│
msg
智能指针

真正传递过去的是消息数据。

不是 Publisher 中那个 C++ 指针地址。


问题 7:为什么这里用 -> 而不是 .?

Publisher 中:

std_msgs::msg::Int32 msg;
msg.data = 5;

这里:

msg
= 对象本身

所以用:

msg.data

Subscriber 中:

std_msgs::msg::Int32::ConstSharedPtr msg;

这里:

msg
= 智能指针

所以使用:

msg->data

可以记成:

对象 .
指针 ->


问题 8:为什么是 ConstSharedPtr,而不是普通 SharedPtr?

SharedPtr 的主要意义之一是:

多个地方可以安全共享对同一个消息对象的生命周期管理。

而 ConstSharedPtr 又进一步表示:

callback 主要以只读方式访问这条消息。

所以:

msg->data

可以读取。

设计意图是不随意修改收到的原始消息。


问题 9:智能指针是不是意味着 ROS2 全程零拷贝?

不能这样理解。

使用:

ConstSharedPtr

可以减少 callback 边界上不必要的完整对象复制,并方便管理消息生命周期。

但是 Publisher 到 Subscriber 的完整通信过程,还可能涉及:

序列化
DDS传输
反序列化
进程间通信
进程内优化
零拷贝机制

具体是否发生复制,与通信方式和 ROS2 配置有关。

因此更准确的说法是:

ConstSharedPtr 让 callback 可以共享已有的接收消息对象,而不必简单地每次都按值复制整个消息。


问题 10:[this] 和 msg 又是什么关系?

Subscriber callback:

[this](std_msgs::msg::Int32::ConstSharedPtr msg)
{
receiveNumber(msg);
}

这里:

[this]

和:

msg

是两件完全不同的事。

this:

this
↓
当前NumberSubscriber对象

它回答:

谁在处理消息?

而 msg:

msg
↓
当前收到的Int32消息对象

它回答:

处理什么消息?

所以可以记:

this
= 我是谁

msg
= 我收到了什么

例如:

receiveNumber(msg);

可以理解为:

this
找到当前NumberSubscriber对象

msg
提供当前收到的消息

↓
当前对象处理这条消息


最后的完整数据流

Publisher:

std_msgs::msg::Int32 msg;
msg.data = 5;

publisher_->publish(msg);

概念流程:

Publisher端
msg.data = 5
↓
Publisher.publish(msg)
↓
ROS2 / DDS通信
↓
/number Topic
↓
Subscription监听到消息
↓
Subscriber callback变为可执行
↓
Executor执行callback
↓
ROS2提供ConstSharedPtr msg
↓
msg指向Subscriber侧收到的Int32对象
↓
msg->data
↓
得到5

因此最重要的四句话是:

  • Publisher 传递的是消息数据,不是 Publisher 内存中的对象地址。

  • Subscription 才负责监听 /number Topic,msg 智能指针并不指向 Topic。

  • Subscriber callback 中的 msg 指向的是这一次收到的消息对象。

  • 使用智能指针可以避免 callback 处不必要的大对象复制,并帮助管理消息对象生命周期。

  • 理解了这四点,ROS2 Subscriber 中的:

    std_msgs::msg::Int32::ConstSharedPtr msg

    就不再是一段需要死记的语法,而是有明确工程意义的设计。

    赞(0)
    未经允许不得转载:171主机测评 » C++ 补充说明:ROS2 Subscriber 为什么常用智能指针接收消息?
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址