Skip to content

Commit 3ee04f3

Browse files
committed
add many sects
1 parent d833f5b commit 3ee04f3

29 files changed

+11437
-5
lines changed

docs/cpp_lifetime.md

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
# 深入理解析构函数与生命周期
2+
3+
[TOC]
4+
5+
## C++ 对象生命周期
6+
7+
C++ 中一个类可以具有构造函数和析构函数。
8+
9+
- 构造函数固定为 `类名(构造函数参数列表)`
10+
- 析构函数固定为 `~类名()`
11+
12+
- 构造函数和析构函数都没有返回值类型。
13+
- 析构函数不得拥有任何参数,但构造函数可以有。
14+
15+
```cpp
16+
struct Class {
17+
Class() {
18+
puts("构造函数");
19+
}
20+
21+
~Class() {
22+
puts("析构函数");
23+
}
24+
};
25+
```
26+
27+
```cpp
28+
int main() {
29+
puts("进入 main");
30+
Class c;
31+
puts("离开 main");
32+
}
33+
```
34+
35+
运行结果:
36+
37+
```
38+
进入 main
39+
构造函数
40+
离开 main
41+
析构函数
42+
```
43+
44+
这是 C++ 中最基本的现象。每当一个对象被创建时,会调用构造函数,每当一个对象离开定义了他的函数体时,会调用析构函数。
45+
46+
> 函数体指的是从 `{``}` 之间的代码块。
47+
48+
其中构造函数中通常负责创建资源,析构函数中通常销毁资源。对于智能指针和 vector 而言,这个资源就是内存。
49+
50+
为什么要及时销毁不用的资源?只分配不释放,一个程序占用的内存和其他各种资源就会越来越多,这种程序如果长期运行,会吃光整个系统的所有资源然后被 Linux 内核视为危险进程而杀死。除非你的程序只会运行一会会,如果是长期运行的程序,例如服务器,必须严格管理所有自己曾经分配过的内存,不用时就立即释放,不要占着茅坑不拉史。
51+
52+
`}` 被誉为**最伟大的运算符**,就是因为他可以触发析构函数,帮你自动释放掉资源,你就不用自己费心手动释放内存,和其他各种资源了。
53+
54+
## 三大存储周期
55+
56+
在进一步深入之前,我们必须明确以下术语:自动存储周期、动态存储周期、静态存储周期。
57+
58+
变量定义在不同的位置,其生命周期(构造函数和析构函数调用的时机)会有所不同。
59+
60+
比如一个变量定义在函数体内、类体内、通过 new 创建,之类的。
61+
62+
1. 自动存储周期,这种变量直接定义在**函数体**内。俗称“栈上”或“局部变量”
63+
64+
```cpp
65+
void func() {
66+
Class a; // a 是自动存储周期
67+
}
68+
```
69+
70+
- 构造时机:当变量定义时被调用。
71+
- 析构时机:当变量所在的 `{}` 代码块执行到 `}` 处时调用。
72+
73+
2. 动态存储周期,这种变量通过 new 来创建。俗称“堆上”或“堆对象”
74+
75+
```cpp
76+
void func() {
77+
Class *p = new Class; // *p 是动态存储周期
78+
delete p; // 释放动态分配的内存
79+
}
80+
```
81+
82+
- 构造时机:当变量通过 new 创建时被调用。
83+
- 析构时机:当 delete 被调用时被调用。
84+
85+
> 特别注意,`p` 依然是“栈上变量”,`p` 指向的 `*p` 才是“堆上变量”!
86+
87+
> 用律师语再说一遍:`p` 是自动存储周期,`p` 指向的 `*p` 才是动态存储周期!(白律师最满意的一集)
88+
89+
指针本身,和指针指向的对象,是两个东西,不要混淆。
90+
91+
`p` 本身会随着 func 的 `}` 而析构,但是 `*p` 的类型是 `Class *`,是一个 C 语言原始指针,原始指针属于 C 语言原始类型,没有析构函数。也就是说,抵达 `}` 时,`p` 名义上会析构,但是他没有析构函数,并不会产生任何作用。这一切和 `p` 指向的对象 `*p` 没有任何关系,你需要手动 delete 才会调用到 `*p` 的析构函数,并释放分配的内存。
92+
93+
- new 分为两部分:内存分配 + **对象构造**
94+
- delete 分为两部分:**对象析构** + 内存释放
95+
96+
智能指针的优势在于,智能指针是个 C++ 类,具有定制的析构函数。当 `}` 抵达,**智能指针本身**由于自动存储周期的规则析构时,其会 `delete p`,利用动态存储周期的规则,触发**智能指针指向对象**的析构函数,也就是从而调用 `*p` 的析构函数。
97+
98+
3. 静态存储周期,这种变量又要具体分三种情况,俗称“全局变量”或“静态变量”
99+
100+
(1) 定义在**名字空间**内,不论是不是 static 或 inline(在名字空间中,static 和 inline 影响的只是“符号可见性”,而不是存储周期)
101+
102+
```cpp
103+
namespace hello {
104+
Class s; // s 是静态存储周期
105+
static Class s; // s 是静态存储周期
106+
inline Class s; // s 是静态存储周期
107+
}
108+
```
109+
110+
- 构造时机:当程序启动时调用(main 函数之前);对 DLL 来说则是 DLL 首次加载时调用。
111+
- 析构时机:当程序退出时调用(main 函数之后)。
112+
113+
(2) 注意,**全局名字空间**是一个特殊的**名字空间**,外面没有包裹任何 `namespace` 时就属于这种情况,俗称“全局变量”。所以下面这种也属于“在 (全局) 名字空间内”:
114+
115+
```cpp
116+
Class s; // s 是静态存储周期
117+
static Class s; // s 是静态存储周期
118+
inline Class s; // s 是静态存储周期
119+
```
120+
121+
(3) 定义在类内的静态成员变量,也就是通过 static 修饰过的成员变量(在类内,static 就影响存储周期了,inline 继续只影响“符号可见性”)
122+
123+
```cpp
124+
struct Other {
125+
static Class s;
126+
};
127+
Class Other::s; // s 是静态存储周期
128+
129+
struct Other {
130+
inline static Class s; // s 是静态存储周期
131+
};
132+
133+
struct Other {
134+
Class a; // a 不是静态存储周期,而是跟随其所属的 Other 结构体的存储周期
135+
};
136+
```
137+
138+
4. 定义在类内的成员变量(没有 static 的),跟随所属类的存储周期
139+
140+
```cpp
141+
struct Other {
142+
Class a; // a 跟随 Other 结构体的存储周期
143+
};
144+
145+
Other o; // o.a 是静态存储周期
146+
147+
int main() {
148+
Other o; // o.a 是自动存储周期
149+
Other *p; // p->a 是动态存储周期
150+
}
151+
```
152+
153+
- 构造时机:当 Other 结构体构造时调用。
154+
- 析构时机:当 Other 结构体析构时调用。
155+
156+
## 总结
157+
158+
- 自动存储周期 - 函数的局部变量,自动析构
159+
- 动态存储周期 - 通过 new 创建的,delete 时析构
160+
- 静态存储周期 - 全局变量,程序结束时析构
161+
162+
```cpp
163+
```
164+
165+
## 析构函数的逆天大坑
166+
167+
定义了析构函数,就**必须删除移动构造函数、移动赋值函数、拷贝构造函数、拷贝赋值函数**
168+
169+
原因很复杂,整个故事要从 boost 当年如何设计出右值引用到图灵的停机问题讲起,讲了你也记不住,只需要记住结论:
170+
171+
如果你要定义析构函数,就**必须删除移动构造函数、移动赋值函数、拷贝构造函数、拷贝赋值函数**
172+
173+
## 虚类的析构函数必须是虚的
174+
175+
- `-Wnon-virtual-dtor`
176+
- `-Wdelete-non-virtual-dtor`
177+
178+
TODO: 介绍

docs/cpp_memory.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
## 真正的内存模型!
2+
3+
```cpp
4+
void modify(int *pa) {
5+
int *pb = pa + 1;
6+
*pb = 9;
7+
}
8+
9+
int func() {
10+
int a = 4;
11+
int b = 5;
12+
modify(&a);
13+
return b; // 5 还是 9?
14+
}
15+
```

0 commit comments

Comments
 (0)