上一课标准答案:
robot.run();
和:
p->run();
如果:
Robot* p = &robot;
那么两句最终操作的是同一个 robot 对象。区别只是:
robot.run()
↑
手里直接拿着对象,所以用 .
p->run()
↑
手里拿着对象地址,所以用 ->
而:
(*p).run();
和:
p->run();
基本等价。
因为:
*p
↓
先通过地址找到对象
(*p).run()
↓
再调用这个对象的 run()
-> 只是把这个过程写得更方便。
第 27 课:智能指针 shared_ptr
这一课非常关键,因为 ROS2 C++ 里到处都是它。
你以后很快会看到:
auto node = std::make_shared<MyNode>();
如果不理解智能指针,ROS2 代码从这里开始就容易看不懂。
所以今天重点只解决:
shared_ptr到底是什么,为什么 ROS2 喜欢用它。
1. 先复习普通指针
我们已经学过:
Robot robot;
Robot* p = &robot;
这里:
Robot* p
表示:
p是一个指针。
里面保存:
robot 的地址
于是:
p->run();
就能找到 robot 并调用它的函数。
2. 但普通指针有个问题
考虑:
Robot* p;
它只是保存地址。
它并不会自动帮你解决:
这个对象什么时候应该被销毁?
这个问题叫:
对象生命周期
我们先用一个生活例子。
假设:
Robot对象 = 一台设备
指针 = 写着设备仓库地址的一张纸
普通指针只是告诉你:
"设备在哪里。"
但它不负责:
"设备最后谁负责处理?"
3. 以前 C++ 可以这样创建对象
你以后可能看到:
Robot* p = new Robot();
这里:
new Robot()
表示:
动态创建一个 Robot 对象。
然后返回它的地址。
于是:
p
保存地址。
可以:
p->run();
但是最后通常必须:
delete p;
意思:
把之前动态创建的对象释放掉。
4. 为什么危险?
因为程序员可能忘记:
delete p;
于是这个对象占用的内存一直没有被释放。
这叫:
内存泄漏
或者还有更糟的情况:
delete p;
delete p;
重复释放。
又或者:
delete p;
p->run();
对象已经没了,还继续用地址。
这都很危险。
5. 所以现代 C++ 出现智能指针
核心思想:
不让程序员天天手动管理
delete。
智能指针帮你管理对象生命周期。
今天先学 ROS2 最常见的:
std::shared_ptr
6. 第一段智能指针代码
#include <memory>
std::shared_ptr<Robot> p;
先拆。
std::shared_ptr
表示:
一种智能指针。
后面的:
<Robot>
表示:
这个智能指针管理的是
Robot对象。
所以:
std::shared_ptr<Robot> p;
可以理解成:
p是一个智能指针,可以管理一个 Robot 对象。
7. 为什么又出现 <Robot>?
你在 vector 已经见过:
std::vector<double>
意思:
装
double的 vector。
类似:
std::shared_ptr<Robot>
就是:
管理
Robot的 shared_ptr。
暂时不需要深入"模板"理论。
你只需要会读。
8. 真正创建对象通常不用 new
现代 C++ 更推荐:
auto p = std::make_shared<Robot>();
这句看着复杂。
我们一层一层拆。
9. 先看最里面
std::make_shared<Robot>()
大概可以理解成:
创建一个
Robot对象,并交给shared_ptr管理。
它会返回一个:
std::shared_ptr<Robot>
10. 那 auto 是什么?
以前我们写:
int a = 5;
明确告诉 C++:
a 是 int
但:
auto p = std::make_shared<Robot>();
这里 auto 表示:
让编译器根据右边自动推断变量类型。
编译器看到右边返回:
std::shared_ptr<Robot>
所以自动知道:
p
的类型是:
std::shared_ptr<Robot>
也就是说:
auto p = std::make_shared<Robot>();
可以先理解成:
std::shared_ptr<Robot> p =
std::make_shared<Robot>();
11. 创建以后怎么使用?
虽然:
p
不是普通 Robot 对象,
但它"指向/管理"一个 Robot。
所以仍然:
p->run();
这也是为什么前面专门给你讲:
->
12. 完整例子
#include <iostream>
#include <memory>
class Robot
{
public:
void run()
{
std::cout << "Robot running" << std::endl;
}
};
int main()
{
auto robot = std::make_shared<Robot>();
robot->run();
return 0;
}
输出:
Robot running
13. 内存图怎么理解?
可以先画成:
robot
┌────────────────────────┐
│ shared_ptr │
│ 管理Robot对象 │
└────────────┬───────────┘
│
▼
┌───────────┐
│ Robot对象 │
│ run() │
└───────────┘
所以:
robot->run();
就是:
通过智能指针找到 Robot 对象,然后调用
run()。
14. 它和普通指针最大的区别是什么?
普通指针:
Robot* p;
核心功能:
保存地址。
但通常不负责对象生命周期。
智能指针:
std::shared_ptr<Robot> p;
除了能找到对象,还承担:
管理对象生命周期
的责任。
这才是重点。
15. 为什么叫 shared?
shared 的意思是:
共享。
也就是说:
多个
shared_ptr可以共同管理同一个对象。
例如:
auto p1 = std::make_shared<Robot>();
auto p2 = p1;
现在:
p1 ─────┐
▼
Robot
p2 ─────┘
两个智能指针共同指向同一个 Robot。
16. 所以修改的是不是同一个对象?
是。
例如:
class Robot
{
public:
int state = 0;
};
然后:
auto p1 = std::make_shared<Robot>();
auto p2 = p1;
p1->state = 10;
那么:
std::cout << p2->state;
也会看到:
10
因为:
p1和p2管理的是同一个对象。
17. 那什么时候销毁 Robot?
这是 shared_ptr 最核心的地方。
它会记录:
现在有多少个
shared_ptr在管理这个对象。
例如:
p1 ──┐
├── Robot
p2 ──┘
现在大概有:
2 个拥有者
如果:
p2 没了
还有:
p1
所以 Robot 继续存在。
直到:
最后一个
shared_ptr也没了
Robot 对象才会被自动销毁。
18. 可以理解成"钥匙数量"
比如一间实验室。
Robot对象
=
实验室
现在:
p1 有钥匙
p2 有钥匙
只要还有人在合法持有这间实验室:
实验室就继续保留。
最后一个持有人也离开后:
系统自动把资源释放。
这个类比能帮助你理解 shared。
19. 为什么 ROS2 很喜欢这种设计?
因为 ROS2 里,一个对象经常不只是被一个地方使用。
例如一个:
Node
Publisher
Subscription
Timer
可能会同时参与:
执行器
回调
事件系统
ROS2内部管理
对象什么时候还能用,什么时候应该销毁,很容易复杂。
所以智能指针能够帮助程序自动管理生命周期。
20. 你以后第一眼就会看到这个
ROS2 很典型:
auto node = std::make_shared<MyNode>();
现在不要把它当成一句"神秘 ROS2 语法"。
拆开就是:
MyNode
↓
一个类
std::make_shared<MyNode>()
↓
创建一个 MyNode 对象,
用 shared_ptr 管理
auto node
↓
让编译器自动判断 node 的类型
所以:
node->xxx();
你现在已经知道为什么用:
->
了。
21. ROS2 中还有这种写法
以后你会看到:
rclcpp::Publisher<...>::SharedPtr publisher_;
别被长名字吓到。
核心依旧是:
publisher_是一个被智能指针管理的 Publisher 对象。
所以以后可能:
publisher_->publish(msg);
本质还是:
智能指针
→
找到 Publisher 对象
→
调用 publish()
我们后面正式进入 ROS2 时再逐段拆。
22. 那 unique_ptr 又是什么?
还有一种:
std::unique_ptr
unique:
唯一。
所以:
std::unique_ptr<Robot>
表示:
一个 Robot 通常只有一个所有者。
例如:
auto p =
std::make_unique<Robot>();
23. shared_ptr 和 unique_ptr 先这样区分
unique_ptr
只有一个主要拥有者
↓
"这个对象归我一个人管理"
而:
shared_ptr
可以有多个拥有者
↓
"这个对象大家共同管理"
24. 对你当前 ROS2 目标,哪个先学深?
答案:
shared_ptr优先。
因为你进入 ROS2 后会高频碰到:
std::shared_ptr
以及各种:
SharedPtr
而 unique_ptr 你也必须认识,但暂时不用特别深入。
25. 你现在不需要研究什么?
暂时不要钻:
shared_ptr控制块
引用计数原子操作
weak_ptr循环引用内部实现
allocator
自定义deleter
这些是更深入 C++ 的内容。
与你当前:
VLA + ROS2 + 机器人
的投入产出比不高。
以后真的遇到再补。
本课你必须真正理解的五句话
第一:
Robot* p
是普通指针。
第二:
std::shared_ptr<Robot> p
是管理 Robot 对象生命周期的智能指针。
第三:
auto p = std::make_shared<Robot>();
创建一个 Robot,并由 shared_ptr 管理。
第四:
p->run();
通过智能指针访问 Robot 的成员。
第五:
最后一个管理对象的
shared_ptr消失后,对象通常会自动释放。
这五句目前够用了。
本课练习
1
auto robot =
std::make_shared<Robot>();
这里 robot 是普通 Robot 对象,还是智能指针?
2
为什么:
robot->run();
使用 -> 而不是:
robot.run();
3
auto p1 = std::make_shared<Robot>();
auto p2 = p1;
p1 和 p2 管理的是两个不同 Robot,还是同一个 Robot?
4
一句话说明:
shared_ptr 和普通裸指针 Robot*
最大的区别是什么。
下一课我们不去深入 unique_ptr,而直接进入对 ROS2/VLA 更重要的:
第 28 课:lambda 表达式
你以后 ROS2 很容易看到:
[this](const Msg::SharedPtr msg)
{
...
}
这一句同时涉及:
lambda
this
shared_ptr
回调函数
第一次见非常吓人。
所以下一课我会从最简单的:
[]()
{
std::cout << "Hello";
}
开始,一步一步把它拆开,然后直接连接到:
ROS2订阅相机/JointState
→
消息到达
→
自动执行回调