Skip to content

Commit 11aa457

Browse files
feat: update chapters
1 parent 40f2547 commit 11aa457

14 files changed

Lines changed: 2005 additions & 20 deletions

drafts/Content-Table-Draft.md

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -71,31 +71,30 @@
7171
- [x] 8.3 std::shared_ptr在嵌入式中的考虑
7272
- [x] 8.4 自定义删除器(Custom Deleter)
7373
- [x] 8.5 引用计数的实现与性能
74-
- [ ] 8.6 作用域守卫(Scope Guard)模式
74+
- [x] 8.6 作用域守卫(Scope Guard)模式
7575

7676
#### 第8章 容器与数据结构
7777

78-
- 9.1 STL容器的性能分析
79-
- 9.2 std::array:编译期固定大小数组
80-
- 9.3 std::span(C++20):非拥有型视图
81-
- 9.4 循环缓冲区实现
82-
- 9.5 侵入式容器设计
83-
- 9.6 etl库(Embedded Template Library)介绍
84-
- 9.7 自定义分配器(Allocator)
78+
- [x] 9.1 std::array:编译期固定大小数组
79+
- [x] 9.2 std::span(C++20):非拥有型视图
80+
- [x] 9.3 循环缓冲区实现
81+
- [x] 9.4 侵入式容器设计
82+
- [x] 9.5 etl库(Embedded Template Library)介绍
83+
- [x] 9.6 自定义分配器(Allocator)
8584

8685
------
8786

8887
## **第四部分:现代C++特性应用**
8988

9089
#### 第9章 类型安全与强类型
9190

92-
- 10.1 enum class:强类型枚举
93-
- 10.2 类型安全的寄存器访问
94-
- 10.3 std::variant:类型安全的联合体(C++17)
95-
- 10.4 std::optional:可选值语义(C++17)
96-
- 10.5 std::expected:错误处理新范式(C++23)
97-
- 10.6 类型别名与using声明
98-
- 10.7 字面量运算符与自定义单位
91+
- [x] 10.1 enum class:强类型枚举
92+
- [x] 10.2 类型安全的寄存器访问
93+
- [x] 10.3 std::variant:类型安全的联合体(C++17)
94+
- [x] 10.4 std::optional:可选值语义(C++17)
95+
- [x] 10.5 std::expected:错误处理新范式(C++23)
96+
- [ ] 10.6 类型别名与using声明
97+
- [ ] 10.7 字面量运算符与自定义单位
9998

10099
#### 第10章 函数式编程元素
101100

tutorial/核心:现代嵌入式C++教程/Chapter6/7 引用计数.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
# 引用计数的实现与性能(适合嵌入式 C++ 的可直接发布博客稿)
1+
# 引用计数的实现与性能
22

33
写引用计数的文章就像在讲“谁送快递给谁付钱”——每多一个人拿着快递,你的账本上就多记一笔;最后那个人把快递丢在门口,账本清零,快递就可以销毁。区别是:在嵌入式世界里,这个“账本”得很小心地放在口袋里,不能丢、不能被并发多人改错账,还要尽量别让 CPU 为了记一笔账掉进长时间的排队。
44

55
------
66

7-
## 什么是引用计数(一句话版,幽默但严肃)
7+
## 什么是引用计数
88

99
引用计数(reference counting)就是给对象装个小计数器,每当有人“拿走”一份引用就 `++`,放回去就 `--`;当计数变成 0,表明没人需要了,就销毁对象。简单、直观、无需全局垃圾回收。但要注意:两个对象互相持有引用——那就是经典的“情侣互相缠着不放(cycle)”,永远不会变成 0,需要我们手动拆或用弱引用(weak reference)。
1010

@@ -120,17 +120,17 @@ private:
120120

121121
------
122122

123-
## 嵌入式实践建议(直白、给到具体工程决策)
123+
## 嵌入式实践建议
124124

125-
如果你在做嵌入式 C++,可以按下面的优先级来选择实现策略(字面而非列表的描述):
125+
如果你在做嵌入式 C++,可以按下面的优先级来选择实现策略
126126

127127
先判断是否有并发与中断场景。如果没有并发,使用侵入式非原子计数最省。若有并发但系统是单核且可在关键段禁中断,把计数器的修改放在关中断的临界区里(成本比全原子低,适合对延迟敏感但只有少量竞争的场景)。对真正的多核并发,使用 `std::atomic` 的实现,尽量使用侵入式以减少内存访问;如果不能侵入并且堆分配昂贵,考虑对象池或预分配 control-block 的策略。
128128

129129
此外,要避免循环引用:在父子关系里,父持有子强引用,子持父弱引用;或者在文档中明确约定谁负责生命周期(simplicity beats cleverness)。
130130

131131
------
132132

133-
## 简单性能微基准建议(如何测量)
133+
## 简单性能微基准建议
134134

135135
不需要复杂工具,写个基准程序测两件事:单线程下每次拷贝/销毁耗时(测 `new+sharedptr` vs `intrusive`),以及 N 线程同时频繁拷贝/释放的吞吐。测量使用 `std::chrono::steady_clock`,而且要注意热身和大量重复(百万级)以消除计时误差。注意记录 CPU 占用和功耗(若设备可测)。
136136

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
# 嵌入式现代C++教程——作用域守卫(Scope Guard):让清理代码乖乖在“出门顺手关灯”那一刻执行
2+
3+
写嵌入式代码时,总会遇到这样的人生真相:你在函数某处申请了资源(打开外设、上锁、禁中断、分配缓冲……),后来代码分叉、提前 `return`、甚至抛出异常——结果忘了释放/恢复。结果就是内存泄漏、死锁、外设状态奇怪,或者你被老大盯着问“为什么这段代码跑了两分钟还没返回”。
4+
5+
作用域守卫(Scope Guard)就是为了解决这个问题的——把“离开当前作用域时必须做的事”绑定在一个对象的析构函数上:只要对象离开作用域,析构函数就会执行,清理也就稳了。它是 RAII 的小而美的实用变体,尤其适合嵌入式场景(没有堆分配、追求确定性)。
6+
7+
------
8+
9+
# 先看最简单的:lambda + 小模板
10+
11+
这是最常见的现代 C++ 写法(C++11 起就能用)。核心思想:封装一个可调用对象,析构时调用它(如果没有被取消)。
12+
13+
```cpp
14+
#include <utility>
15+
#include <exception>
16+
#include <cstdlib> // for std::terminate
17+
18+
template <typename F>
19+
class ScopeGuard {
20+
public:
21+
explicit ScopeGuard(F&& f) noexcept
22+
: fn_(std::move(f)), active_(true) {}
23+
24+
// 不允许拷贝(避免重复调用)
25+
ScopeGuard(const ScopeGuard&) = delete;
26+
ScopeGuard& operator=(const ScopeGuard&) = delete;
27+
28+
// 允许移动
29+
ScopeGuard(ScopeGuard&& other) noexcept
30+
: fn_(std::move(other.fn_)), active_(other.active_) {
31+
other.dismiss();
32+
}
33+
34+
~ScopeGuard() noexcept {
35+
if (active_) {
36+
try {
37+
fn_();
38+
} catch (...) {
39+
// 析构函数不可抛出 —— 在嵌入式中通常直接终止
40+
std::terminate();
41+
}
42+
}
43+
}
44+
45+
void dismiss() noexcept { active_ = false; }
46+
47+
private:
48+
F fn_;
49+
bool active_;
50+
};
51+
52+
// 辅助函数方便模板推导
53+
template <typename F>
54+
ScopeGuard<F> make_scope_guard(F&& f) {
55+
return ScopeGuard<F>(std::forward<F>(f));
56+
}
57+
```
58+
59+
用法示例:
60+
61+
```cpp
62+
void foo() {
63+
auto g = make_scope_guard([](){ close_device(); });
64+
// do something...
65+
if (error) return; // close_device 会被保证调用
66+
g.dismiss(); // 如果想提前取消清理
67+
}
68+
```
69+
70+
幽默注:`dismiss()` 就是给守卫放假,不让它在离职那天烦你。
71+
72+
------
73+
74+
# 成功/失败分支:`scope_success``scope_fail`
75+
76+
有时候你只想在函数“正常返回”(no exception)时做事,或者只在**抛异常**时处理。C++17 提供了 `std::uncaught_exceptions()` 来判断析构时是否处于异常传播中。基于它,我们可以实现 `scope_exit`(总是执行)、`scope_success`(仅在没有异常时执行)、`scope_fail`(仅在有异常时执行)。
77+
78+
```cpp
79+
#include <exception>
80+
81+
template <typename F>
82+
class ScopeGuardOnExit {
83+
// 同上,始终执行
84+
};
85+
86+
template <typename F>
87+
class ScopeGuardOnSuccess {
88+
public:
89+
explicit ScopeGuardOnSuccess(F&& f) noexcept
90+
: fn_(std::move(f)), active_(true), uncaught_at_construction_(std::uncaught_exceptions()) {}
91+
92+
~ScopeGuardOnSuccess() noexcept {
93+
if (active_ && std::uncaught_exceptions() == uncaught_at_construction_) {
94+
try { fn_(); } catch(...) { std::terminate(); }
95+
}
96+
}
97+
// ... move/dismiss same as above
98+
private:
99+
F fn_;
100+
bool active_;
101+
int uncaught_at_construction_;
102+
};
103+
104+
template <typename F>
105+
class ScopeGuardOnFail {
106+
public:
107+
explicit ScopeGuardOnFail(F&& f) noexcept
108+
: fn_(std::move(f)), active_(true), uncaught_at_construction_(std::uncaught_exceptions()) {}
109+
110+
~ScopeGuardOnFail() noexcept {
111+
if (active_ && std::uncaught_exceptions() > uncaught_at_construction_) {
112+
try { fn_(); } catch(...) { std::terminate(); }
113+
}
114+
}
115+
// ...
116+
private:
117+
F fn_;
118+
bool active_;
119+
int uncaught_at_construction_;
120+
};
121+
```
122+
123+
这样你就可以写:
124+
125+
```cpp
126+
auto on_success = make_scope_guard_success([](){ commit_tx(); });
127+
auto on_fail = make_scope_guard_fail([](){ rollback_tx(); });
128+
```
129+
130+
在嵌入式里如果禁用异常,这俩就没用武之地 —— 但是 `scope_exit`(总是执行)仍然非常有用。
131+
132+
------
133+
134+
# 方便的宏:减少样板代码
135+
136+
写守卫变量名挺烦的,宏可以帮你:
137+
138+
```cpp
139+
#define CONCAT_IMPL(x, y) x##y
140+
#define CONCAT(x, y) CONCAT_IMPL(x, y)
141+
#define SCOPE_GUARD(code) \
142+
auto CONCAT(_scope_guard_, __COUNTER__) = make_scope_guard([&](){ code; })
143+
```
144+
145+
用法:
146+
147+
```cpp
148+
SCOPE_GUARD({ disable_irq(); restore_irq_state(saved); });
149+
// 作用域结束时自动调用
150+
```
151+
152+
在没有 `__COUNTER__` 的编译器上用 `__LINE__` 也行,不过 `__COUNTER__` 更保险。
153+
154+
------
155+
156+
# 例子
157+
158+
1. 禁用中断并保证恢复(伪代码,平台提供 `__disable_irq()` / `__enable_irq()`):
159+
160+
```cpp
161+
void critical_section() {
162+
bool prev = save_and_disable_irq();
163+
auto restore = make_scope_guard([&]{ restore_irq(prev); });
164+
165+
// 关键操作
166+
}
167+
```
168+
169+
1. 上锁/解锁
170+
171+
```cpp
172+
mutex.lock();
173+
auto unlock = make_scope_guard([&]{ mutex.unlock(); });
174+
// 如果函数中途 return,mutex 会被正确解锁
175+
```
176+
177+
1. 临时改变寄存器、并在退出恢复
178+
179+
```cpp
180+
uint32_t old = REG_CTRL;
181+
REG_CTRL = old | ENABLE_BIT;
182+
auto restore_reg = make_scope_guard([=]{ REG_CTRL = old; });
183+
```
184+
185+
------
186+
187+
# 嵌入式注意事项与最佳实践
188+
189+
- **不要分配堆内存**:守卫对象本身应该在栈上,成员不要包含动态分配,嵌入式通常禁用或不喜欢堆。
190+
- **析构函数必须不抛异常**:标准要求析构不能抛出(会导致 `std::terminate` 在异常传播时)。我们的实现用 `try`/`catch(...) { std::terminate(); }` 或者如果你有日志系统可以先记录再终止。另一个选择是静默吞掉异常,但那可能掩盖错误。
191+
- **尽量内联(`inline`**:模板 + `constexpr` / `inline` 有利于编译器优化,不增加运行时开销。
192+
- **对象大小**:实现非常小(一个可调用对象 + 一个 `bool`),对内存敏感的场景适合。避免把大对象捕获到 lambda 中。
193+
- **编译器/标准**:如果你用的是老旧编译器,确保至少支持 C++11(lambda、移动语义)。若要 `scope_success/fail`,需要 C++17 的 `std::uncaught_exceptions()`
194+
- **禁用异常的环境**:如果工程编译时禁用异常(`-fno-exceptions`),`scope_fail`/`scope_success` 不可用。`scope_exit` 仍然适用,仍能保证清理行为。
195+
- **避免在中断上下文做复杂事情**:在 ISR 中创建守卫要小心(栈空间有限、不要调用可能阻塞或分配的函数)。
196+
197+
------
198+
199+
# `std::unique_ptr` 的小技巧(简单场景)
200+
201+
有时候你只是想用现有工具来做简单清理:
202+
203+
```cpp
204+
#include <memory>
205+
206+
auto closer = std::unique_ptr<void, decltype([](void*){ close_fd(fd); })>(nullptr, [](void*){ close_fd(fd); });
207+
```
208+
209+
但这种写法语义上不如专门的 `ScopeGuard` 清晰(模板更适合做任意清理),我提是为了给你多一个“武器库”里的小工具。
210+

0 commit comments

Comments
 (0)