C++ 第 27 课:智能指针 shared_ptr

上一课标准答案:

复制代码
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
→
消息到达
→
自动执行回调
相关推荐
by209991 小时前
学会使用std::string类,并理解其内部是如何管理字符串的详细阐述(上)
c++·笔记·字符串·类和对象·string
沙漠之主1 小时前
C++编程教学设计资料:从入门到实战的完整课程方案
java·前端·c++
朝朝辞暮i1 小时前
C++ 第 29 课:回调函数 Callback
开发语言·c++·算法
笨笨饿2 小时前
140_AI新手村MCP与Skills是干嘛的
开发语言·人工智能·python·stm32·单片机·嵌入式硬件·物联网
Godspeed Zhao2 小时前
现代智能雷达技术14——算法(0)
线性代数·算法·雷达
YOLO数据集集合2 小时前
EvoAgent:面向PR研发治理的自进化Multi-Agent Harness系统
java·开发语言·目标检测·agent·自进化
kaixin_learn_qt_ing2 小时前
C++多态实现原理
c++
可乐ea2 小时前
Agent 模型分级升级路由:用置信度阈值把贵模型省下来
人工智能·算法·机器学习·结构化输出·置信度路由·模型升级路由·大模型成本优化
无名猿2 小时前
unique_ptr 完全指南:独占所有权与零开销
c++·性能优化·内存管理·标准库·现代c++