C++ 智能指针(末):new vs unique_ptr 终极对比 + “指针成员到底是不是创建对象”一次讲透

目录

一、前言

二、结论先行

[三、传统 new/delete 写法(下篇问题源头)](#三、传统 new/delete 写法(下篇问题源头))

[3.1 表面上没问题("工程幻觉")](#3.1 表面上没问题(“工程幻觉”))

[3.2 但工程上隐含 4 个风险点](#3.2 但工程上隐含 4 个风险点)

[❌ 风险 1:必须人工维护 new ↔ delete 对称性](#❌ 风险 1:必须人工维护 new ↔ delete 对称性)

[❌ 风险 2:多分支/异常路径不可控](#❌ 风险 2:多分支/异常路径不可控)

[❌ 风险 3:默认拷贝 = 双重 delete(经典炸点)](#❌ 风险 3:默认拷贝 = 双重 delete(经典炸点))

[❌ 风险 4:插件卸载/多线程回调极易 UAF](#❌ 风险 4:插件卸载/多线程回调极易 UAF)

[四、unique_ptr 写法(中篇思想的工程落地)](#四、unique_ptr 写法(中篇思想的工程落地))

[五、逐点对比:unique_ptr 相比 new 到底改变了什么?](#五、逐点对比:unique_ptr 相比 new 到底改变了什么?)

[对比 1:释放责任从"人"转移给"语言机制"](#对比 1:释放责任从“人”转移给“语言机制”)

[对比 2:异常/return 安全是天然的](#对比 2:异常/return 安全是天然的)

[对比 3:拷贝风险被编译期封死](#对比 3:拷贝风险被编译期封死)

[对比 4:所有权规则"自解释"](#对比 4:所有权规则“自解释”)

[六、最常见误区:ControlCore* ctrl_core_; 和 std::unique_ptr ctrl_core_; 到底是不是在创建对象?](#六、最常见误区:ControlCore* ctrl_core_; 和 std::unique_ptr ctrl_core_; 到底是不是在创建对象?)

[6.1 ControlCore* ctrl_core_; 的含义](#6.1 ControlCore* ctrl_core_; 的含义)

1)含义:

2)总结:

[6.2 std::unique_ptr ctrl_core_; 的含义](#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 写法(下篇问题源头)

cpp 复制代码
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:多分支/异常路径不可控

cpp 复制代码
VehicleController() {
  ctrl_core_ = new ControlCore();
  if (init_failed) return; // delete 走不到
}

❌ 风险 3:默认拷贝 = 双重 delete(经典炸点)

cpp 复制代码
VehicleController a;
VehicleController b = a; // 默认拷贝构造
// 两个 ctrl_core_ 指向同一对象 -> 析构 delete 两次 -> 未定义行为

❌ 风险 4:插件卸载/多线程回调极易 UAF

控制器析构了,回调还在用这个裸指针,就会 Use-After-Free。


四、unique_ptr 写法(中篇思想的工程落地)

cpp 复制代码
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:拷贝风险被编译期封死

cpp 复制代码
VehicleController a;
VehicleController b = a; // ❌ 编译期报错(unique_ptr 不可拷贝)

对比 4:所有权规则"自解释"

cpp 复制代码
std::unique_ptr<ControlCore> ctrl_core_;

看到就知道:唯一拥有、不可共享、生命周期绑定。


六、最常见误区:ControlCore* ctrl_core_;std::unique_ptr<ControlCore> ctrl_core_; 到底是不是在创建对象?

很多人会误以为下面两行是在"创建 ControlCore 对象":

cpp 复制代码
ControlCore* ctrl_core_;
std::unique_ptr<ControlCore> ctrl_core_;

但它们都不是创建 ControlCore 对象,它们做的事情是:

VehicleController 这个类里声明一个成员变量

  • 裸指针:只是"地址槽位"

  • unique_ptr:是"带唯一所有权语义的管理器槽位"

用来保存(或管理)某个 ControlCore 对象。

真正"创建对象"的动作,发生在 new / make_unique 那一行,而不是这两行。

6.1 ControlCore* ctrl_core_; 的含义

cpp 复制代码
ControlCore* ctrl_core_;

1)含义:

  • 声明一个"裸指针成员变量"

  • 这个变量里能放一个地址(ControlCore*

  • 通过这个地址可以访问某个 ControlCore 对象

注意:它不负责创建对象,也不负责释放对象。

也就是说,这行只是:

"我准备留一个地方,未来可以存放一个 ControlCore 的地址。"

对象一般是后面才创建并赋值的,例如:

cpp 复制代码
ctrl_core_ = new ControlCore();     // ✅ 这行才创建对象(堆上)

如果你创建了对象,最终还得手动释放:

cpp 复制代码
delete ctrl_core_;
ctrl_core_ = nullptr;

2)总结:

ControlCore* ctrl_core_; = "我有个地址槽位,但谁拥有对象、谁负责释放完全没写在类型里。"


6.2 std::unique_ptr<ControlCore> ctrl_core_; 的含义

cpp 复制代码
std::unique_ptr<ControlCore> ctrl_core_;

1)含义:

  • 声明一个"独占型智能指针成员变量"

  • 它内部同样存着一个 ControlCore* 地址

  • 但它额外表达并强制一条规则:

如果它指向了一个 ControlCore 对象,那么它就是该对象的唯一所有者(owner),并负责在析构时自动释放。

注意:这行本身也不创建 ControlCore 对象,只是声明一个"管理器变量"。

对象仍然需要你在构造函数里创建,例如:

cpp 复制代码
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_;(声明成员变量)
std::unique_ptr<ControlCore> ctrl_core_; (声明成员变量) ✅(唯一)
ctrl_core_ = new ControlCore(); ❌(仍不明确)
ctrl_core_ = std::make_unique<ControlCore>();

八、总结

new 负责"造对象",但它不负责"谁来管对象"。
unique_ptr 不只是"自动 delete",它是在类型层面写死:
对象的唯一拥有者是谁,生命周期跟谁绑定,能不能拷贝,什么时候必然释放。

所以在车辆运动控制工程里:
如果对象应该与控制器同生共死,用 unique_ptr 不是习惯,而是工程正确性。

相关推荐
草原上唱山歌10 小时前
推荐学习的C++书籍
开发语言·c++·学习
2501_9418752810 小时前
在东京复杂分布式系统中构建统一可观测性平台的工程设计实践与演进经验总结
c++·算法·github
Jacen.L10 小时前
SIGABRT (6) 中止信号详解
c++
王老师青少年编程11 小时前
信奥赛C++提高组csp-s之并查集(案例实践)2
数据结构·c++·并查集·csp·信奥赛·csp-s·提高组
满天星830357711 小时前
【C++】特殊类设计
c++·windows
Ljubim.te12 小时前
inline介绍,宏定义的注意事项以及nullptr
c语言·开发语言·c++
苦藤新鸡12 小时前
6.三数之和
c语言·c++·算法·力扣
Frank_refuel12 小时前
C++之内存管理
java·数据结构·c++
leiming612 小时前
c++ qt开发第一天 hello world
开发语言·c++·qt
@小码农12 小时前
6547网:202512 GESP认证 C++编程 一级真题题库(附答案)
java·c++·算法