目录
一、前言
二、结论先行
三、传统 new/delete 写法(下篇问题源头)
3.1 表面上没问题(“工程幻觉”)
3.2 但工程上隐含 4 个风险点
❌ 风险 1:必须人工维护 new ↔ delete 对称性
❌ 风险 2:多分支/异常路径不可控
❌ 风险 3:默认拷贝 = 双重 delete(经典炸点)
❌ 风险 4:插件卸载/多线程回调极易 UAF
四、unique_ptr 写法(中篇思想的工程落地)
五、逐点对比:unique_ptr 相比 new 到底改变了什么?
对比 1:释放责任从“人”转移给“语言机制”
对比 2:异常/return 安全是天然的
对比 3:拷贝风险被编译期封死
对比 4:所有权规则“自解释”
六、最常见误区:ControlCore* ctrl_core_; 和 std::unique_ptr ctrl_core_; 到底是不是在创建对象?
6.1 ControlCore* ctrl_core_; 的含义
1)含义:
2)总结:
6.2 std::unique_ptr ctrl_core_; 的含义
1)含义:
2)总结:
七、对照表
八、总结
一、前言
承接系列前文:
上篇:裸指针为什么危险(泄漏/异常/多分支/悬空指针)
中篇:RAII 的思想:释放必须绑定生命周期
下篇:车辆运动控制工程实战:unique_ptr / shared_ptr / weak_ptr 在 ROS 中如何落地
这一篇作为末篇,只做两件事:
1)用最小代码把new vs unique_ptr 的工程差异讲到“不可反驳”
2)把最常见误区讲清:ControlCore* ctrl_core_;/unique_ptr<...> ctrl_core_;并不是创建对象
二、结论先行
现代 C++ 不是“不用 new”,
而是“不让你再靠 new/delete 去表达所有权与生命周期”。
三、传统new/delete写法(下篇问题源头)
class VehicleController { public: VehicleController() { ctrl_core_ = new ControlCore(); } ~VehicleController() { delete ctrl_core_; } private: struct ControlCore { void step(double ref, double cur) { (void)ref; (void)cur; } }; ControlCore* ctrl_core_; };3.1 表面上没问题(“工程幻觉”)
构造函数
new析构函数
delete看起来“对称”
3.2 但工程上隐含 4 个风险点
❌ 风险 1:必须人工维护new ↔ delete对称性
这是一条人为约定,不是语言保证。后续改代码很容易漏。
❌ 风险 2:多分支/异常路径不可控
VehicleController() { ctrl_core_ = new ControlCore(); if (init_failed) return; // delete 走不到 }❌ 风险 3:默认拷贝 = 双重 delete(经典炸点)
VehicleController a; VehicleController b = a; // 默认拷贝构造 // 两个 ctrl_core_ 指向同一对象 -> 析构 delete 两次 -> 未定义行为❌ 风险 4:插件卸载/多线程回调极易 UAF
控制器析构了,回调还在用这个裸指针,就会 Use-After-Free。
四、unique_ptr写法(中篇思想的工程落地)
class VehicleController { public: VehicleController() { ctrl_core_ = std::make_unique<ControlCore>(); } private: struct ControlCore { void step(double ref, double cur) { (void)ref; (void)cur; } }; std::unique_ptr<ControlCore> ctrl_core_; };| 名称 | 属于哪一层 | 标准叫法 | 是不是对象 | 具体含义 |
|---|---|---|---|---|
ControlCore | 类型层 | 类型 / 结构体类型 | ❌ 否 | 定义了一种“控制核心”的蓝图,描述它长什么样 |
VehicleController | 类型层 | 类类型 | ❌ 否 | 定义了一种“车辆控制器”的蓝图 |
VehicleController() | 成员函数 | 构造函数 | ❌ 否 | 控制器对象“出生时”执行的初始化逻辑 |
ctrl_core_ | 对象成员 | 成员变量 | ❌(本身不是 ControlCore 对象) | 用来持有/管理某个ControlCore对象 |
std::unique_ptr<ControlCore> | 类型层 | 智能指针类型 | ❌ 否 | 表达“对 ControlCore 的唯一所有权”的类型 |
std::make_unique<ControlCore>() | 表达式 | 对象创建语句 | ✅ 是 | 在堆上创建一个ControlCore对象 |
ControlCore 对象 | 运行时实体 | 对象实例 | ✅ 是 | 真正参与控制计算的那个“实体” |
五、逐点对比:unique_ptr 相比 new 到底改变了什么?
对比 1:释放责任从“人”转移给“语言机制”
| 写法 | 谁负责释放 |
|---|---|
new/delete | 人(靠记忆、靠规范) |
unique_ptr | C++ 生命周期规则(成员析构自动释放) |
对比 2:异常/return 安全是天然的
构造中途throw/return,不会泄漏;裸指针要靠人补齐每条路径。
对比 3:拷贝风险被编译期封死
VehicleController a; VehicleController b = a; // ❌ 编译期报错(unique_ptr 不可拷贝)对比 4:所有权规则“自解释”
std::unique_ptr<ControlCore> ctrl_core_;看到就知道:唯一拥有、不可共享、生命周期绑定。
六、最常见误区:ControlCore* ctrl_core_;和std::unique_ptr<ControlCore> ctrl_core_;到底是不是在创建对象?
很多人会误以为下面两行是在“创建 ControlCore 对象”:
ControlCore* ctrl_core_; std::unique_ptr<ControlCore> ctrl_core_;但它们都不是创建ControlCore对象,它们做的事情是:
在
VehicleController这个类里声明一个成员变量
裸指针:只是“地址槽位”
unique_ptr:是“带唯一所有权语义的管理器槽位”
用来保存(或管理)某个
ControlCore对象。真正“创建对象”的动作,发生在
new/make_unique那一行,而不是这两行。
6.1 ControlCore* ctrl_core_;的含义
ControlCore* ctrl_core_;1)含义:
声明一个“裸指针成员变量”
这个变量里能放一个地址(
ControlCore*)通过这个地址可以访问某个
ControlCore对象
注意:它不负责创建对象,也不负责释放对象。
也就是说,这行只是:
“我准备留一个地方,未来可以存放一个
ControlCore的地址。”
对象一般是后面才创建并赋值的,例如:
ctrl_core_ = new ControlCore(); // ✅ 这行才创建对象(堆上)如果你创建了对象,最终还得手动释放:
delete ctrl_core_; ctrl_core_ = nullptr;2)总结:
ControlCore* ctrl_core_;= “我有个地址槽位,但谁拥有对象、谁负责释放完全没写在类型里。”
6.2std::unique_ptr<ControlCore> ctrl_core_;的含义
std::unique_ptr<ControlCore> ctrl_core_;1)含义:
声明一个“独占型智能指针成员变量”
它内部同样存着一个
ControlCore*地址但它额外表达并强制一条规则:
如果它指向了一个
ControlCore对象,那么它就是该对象的唯一所有者(owner),并负责在析构时自动释放。
注意:这行本身也不创建ControlCore对象,只是声明一个“管理器变量”。
对象仍然需要你在构造函数里创建,例如:
ctrl_core_ = std::make_unique<ControlCore>(); // ✅ 这行才创建对象(堆上)不同的是:你不需要写 delete,因为当VehicleController析构时:
ctrl_core_成员析构
unique_ptr析构会自动delete它管理的对象
并且unique_ptr有额外的工程保证:
不能拷贝(避免双重 delete)
只能 move(显式转移所有权)
2)总结:
std::unique_ptr<ControlCore> ctrl_core_;= “我有个专属负责人槽位:只要对象归我管,我就负责它的生死。”
七、对照表
| 代码 | 是否创建对象 | 是否表达所有权 | 是否自动释放 |
|---|---|---|---|
ControlCore* ctrl_core_;(声明成员变量) | ❌ | ❌ | ❌ |
| ❌ | ✅(唯一) | ✅ |
ctrl_core_ = new ControlCore(); | ✅ | ❌(仍不明确) | ❌ |
ctrl_core_ = std::make_unique<ControlCore>(); | ✅ | ✅ | ✅ |
八、总结
new负责“造对象”,但它不负责“谁来管对象”。unique_ptr不只是“自动 delete”,它是在类型层面写死:
对象的唯一拥有者是谁,生命周期跟谁绑定,能不能拷贝,什么时候必然释放。所以在车辆运动控制工程里:
如果对象应该与控制器同生共死,用unique_ptr不是习惯,而是工程正确性。