Skip to content
Gains Summary
Main Navigation 首页 / Home
C++ 编程 / C++ Programming
系统与高性能 / Systems & Performance
Web 开发 / Web Development
人工智能 / Artificial Intelligence
工业软件 / Industrial Software
其他内容 / Other Topics
C++ 编程 / C++系统与性能 / SystemsWeb 开发 / Web人工智能 / AI工业软件 / Industrial

外观

Sidebar Navigation

← C++ 编程 / C++ Programming

内存管理 / Memory Management

1. C++ 内存管理总览:先看完整地图 / C++ Memory Management Map

2. 程序内存布局:内存不是只有堆和栈 / Program Memory Layout

3. 栈与函数调用:局部变量为什么会消失 / Stack and Function Calls

4. 堆与动态分配:new 到底做了什么 / Heap and Dynamic Allocation

5. 对象生命周期:地址还在不代表对象还活着 / Object Lifetime

6. RAII 与所有权:让对象负责清理 / RAII and Ownership Design

7. 智能指针与所有权关系:谁拥有,谁只是看 / Smart Pointers and Ownership Graphs

8. 容器、迭代器与内存失效 / Containers, Iterators, and Invalidation

9. allocator 与 PMR:把分配策略抽出来 / Allocators and PMR

10. 内存错误与 Sanitizer:把崩溃变成证据 / Memory Errors and Sanitizers

本页目录

智能指针与所有权关系:谁拥有,谁只是看 / Smart Pointers and Ownership Graphs ​

智能指针不是“更聪明的指针”这么简单。

它们真正解决的是所有权表达:谁负责释放对象,谁只是临时观察对象。

1. 裸指针的问题是语义不够 ​

看接口:

cpp
void process(Widget* widget);
1

单看这个函数声明,你不知道:

text
widget 能不能是 nullptr?
process 会不会 delete widget?
process 会不会把 widget 保存起来?
调用者用完后还要不要释放?
1
2
3
4

裸指针能表达“一个地址”。

但它不能自动表达“谁拥有这个地址上的对象”。

智能指针的价值在于把常见所有权关系写进类型。

text
std::unique_ptr<T>
  独占拥有

std::shared_ptr<T>
  共享拥有

std::weak_ptr<T>
  观察 shared_ptr 管理的对象,不参与拥有
1
2
3
4
5
6
7
8

2. unique_ptr:一个对象,一个明确拥有者 ​

最常用的智能指针是 std::unique_ptr。

cpp
#include <memory>
#include <string>

int main() {
    auto name = std::make_unique<std::string>("Alice");
}
1
2
3
4
5
6

这里的关系是:

text
name
  |
  v
std::string object
1
2
3
4

name 离开作用域时,unique_ptr 析构。

它会释放自己拥有的 std::string。

unique_ptr 不能复制:

cpp
auto a = std::make_unique<int>(42);
// auto b = a; // 错
1
2

因为复制会造成两个 owner 都以为自己拥有同一个对象。

它可以移动:

cpp
auto a = std::make_unique<int>(42);
auto b = std::move(a);
1
2

移动后的所有权关系:

text
before

a ---> int(42)

after

a ---> null
b ---> int(42)
1
2
3
4
5
6
7
8

a 仍然是有效的 unique_ptr 对象。

但它不再拥有那个 int。

3. 函数参数怎样表达 unique ownership ​

借用对象:

cpp
void print(const Widget& widget);
1

可能为空的借用:

cpp
void print(const Widget* widget);
1

接管所有权:

cpp
void install(std::unique_ptr<Widget> widget);
1

创建并返回所有权:

cpp
std::unique_ptr<Widget> create_widget() {
    return std::make_unique<Widget>();
}
1
2
3

这比裸指针更清楚。

text
参数是 unique_ptr
  函数获得所有权

返回 unique_ptr
  调用者获得所有权

参数是引用或裸指针
  通常只是借用,除非接口文档另有说明
1
2
3
4
5
6
7
8

4. shared_ptr:共享拥有 ​

有时对象确实需要多个 owner。

比如多个任务共享同一份配置对象:

cpp
#include <memory>
#include <string>
#include <vector>

struct Config {
    std::string path;
};

int main() {
    auto config = std::make_shared<Config>();

    std::shared_ptr<Config> a = config;
    std::shared_ptr<Config> b = config;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

关系:

text
config ----+
a ---------+----> control block ----> Config object
b ---------+
1
2
3

shared_ptr 通常有一个控制块。

控制块里会记录:

text
有多少 shared_ptr 正在拥有对象
有多少 weak_ptr 正在观察控制块
如何删除对象
1
2
3

当最后一个 shared_ptr 离开时,对象会被销毁。

5. shared_ptr 的代价 ​

shared_ptr 很方便,但不是默认答案。

它的成本包括:

text
需要维护引用计数
复制 shared_ptr 会增加计数
销毁 shared_ptr 会减少计数
多线程环境下计数更新通常需要原子操作
所有权关系可能变得不清楚
循环引用会导致泄漏
1
2
3
4
5
6

所以优先顺序通常是:

text
能用局部对象,就用局部对象
能用容器拥有,就用容器
需要独占动态对象,用 unique_ptr
确实需要共享生命周期,再用 shared_ptr
1
2
3
4

6. 循环引用为什么会泄漏 ​

看一个双向关系:

cpp
#include <memory>

struct Node {
    std::shared_ptr<Node> next;
    std::shared_ptr<Node> prev;
};

int main() {
    auto a = std::make_shared<Node>();
    auto b = std::make_shared<Node>();

    a->next = b;
    b->prev = a;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

离开 main() 时,局部变量 a 和 b 会销毁。

但对象内部还有彼此的 shared_ptr。

text
a object --shared--> b object
   ^                  |
   |                  |
   +------shared------+
1
2
3
4

引用计数不会降到 0。

两个对象互相把对方“留住”。

这就是循环引用。

7. weak_ptr:观察但不拥有 ​

解决上面的双向关系,通常让一边不拥有:

cpp
#include <memory>

struct Node {
    std::shared_ptr<Node> next;
    std::weak_ptr<Node> prev;
};
1
2
3
4
5
6

weak_ptr 不增加 shared owner 计数。

使用时需要先 lock():

cpp
if (auto p = node.prev.lock()) {
    // p 是 shared_ptr,说明对象还活着
}
1
2
3

为什么要 lock()?

因为 weak_ptr 只是观察。

它观察的对象可能已经被最后一个 shared_ptr 销毁。

lock() 的含义是:

text
如果对象还活着,就临时拿到一个 shared_ptr。
如果对象已经没了,就得到空 shared_ptr。
1
2

8. make_unique 和 make_shared ​

推荐:

cpp
auto p = std::make_unique<Widget>();
auto q = std::make_shared<Widget>();
1
2

而不是:

cpp
std::unique_ptr<Widget> p(new Widget);
std::shared_ptr<Widget> q(new Widget);
1
2

原因包括:

text
代码更短,所有权更清楚
减少手写裸 new
make_shared 通常能把对象和控制块一起分配,效率更好
异常安全更容易保证
1
2
3
4

但 make_shared 也不是永远最好。

如果对象很大,且还有 weak_ptr 长时间存在,对象存储可能因为控制块还在而延迟释放。

普通学习阶段先记住:

text
默认用 make_unique。
需要共享所有权时用 make_shared。
1
2

9. 本篇总结 ​

智能指针要按所有权选择:

text
unique_ptr
  一个明确 owner,最常用

shared_ptr
  多个 owner 共同决定生命周期

weak_ptr
  观察 shared_ptr 管理的对象,打破环

raw pointer / reference
  借用对象,不负责释放
1
2
3
4
5
6
7
8
9
10
11

不要把 shared_ptr 当成“保险起见”。

它表达的是共享生命周期。

如果代码只是临时使用对象,引用或裸指针反而更准确。

最后更新于:

Pager
上一篇6. RAII 与所有权:让对象负责清理 / RAII and Ownership Design
下一篇8. 容器、迭代器与内存失效 / Containers, Iterators, and Invalidation

持续记录,持续成长

Copyright © Tidenflow