在刚开始学习 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
就不再是一段需要死记的语法,而是有明确工程意义的设计。

