From 0571b05cc5653f40a7dd23026471ceba1faf7470 Mon Sep 17 00:00:00 2001 From: Charliechen114514 <725610365@qq.com> Date: Fri, 22 May 2026 09:11:59 +0800 Subject: [PATCH 1/2] feat: remove draft dirent for a clean repo --- drafts/Conten-STM32F103C8T6-Draft.md | 1231 --------------------- drafts/Content-Table-Draft.md | 566 ---------- drafts/Content-Table-TemplateGuide.md | 325 ------ todo/022-core-theory-expansion.md | 1 - todo/030-template-series.md | 105 +- todo/038-template-vol1-basics.md | 118 ++ todo/039-template-vol2-modern.md | 121 ++ todo/040-template-vol3-metaprogramming.md | 114 ++ todo/041-template-vol4-design-patterns.md | 128 +++ todo/042-stm32f1-engineering-prep.md | 174 +++ 10 files changed, 685 insertions(+), 2198 deletions(-) delete mode 100644 drafts/Conten-STM32F103C8T6-Draft.md delete mode 100644 drafts/Content-Table-Draft.md delete mode 100644 drafts/Content-Table-TemplateGuide.md create mode 100644 todo/038-template-vol1-basics.md create mode 100644 todo/039-template-vol2-modern.md create mode 100644 todo/040-template-vol3-metaprogramming.md create mode 100644 todo/041-template-vol4-design-patterns.md create mode 100644 todo/042-stm32f1-engineering-prep.md diff --git a/drafts/Conten-STM32F103C8T6-Draft.md b/drafts/Conten-STM32F103C8T6-Draft.md deleted file mode 100644 index fde089ecc..000000000 --- a/drafts/Conten-STM32F103C8T6-Draft.md +++ /dev/null @@ -1,1231 +0,0 @@ - - - -## 1. 快速索引 - -### 1.1 硬件模块对照表 - -| 硬件模块 | 章节 | 实验产出 | HAL API类型 | -|---------|------|---------|------------| -| 工具链 | 1.1 | 一键编译环境、可下载固件 | arm-none-eabi-gcc, CMake | -| freestanding策略 | 1.2 | 建立freestanding规范 | - | -| 启动文件与链接脚本 | 1.3 | 理解启动流程 | - | -| 最小BSP与板级抽象 | 1.4 | 板级资源管理 | - | -| 时钟树 | 2.1 | 系统时钟配置 | HAL时钟配置API | -| GPIO | 2.2 | LED闪烁、按键读取 | HAL_GPIO初始化/读写 | -| EXTI | 2.3 | 按键中断 | HAL EXTI相关API | -| SysTick | 2.4 | 时间基准 | HAL_GetTick | -| 基础TIM | 3.1 | 周期定时中断 | HAL定时器API | -| PWM | 3.2 | LED呼吸灯、蜂鸣器控制 | HAL PWM API | -| 输入捕获 | 3.3 | 测频器、测脉宽器 | HAL输入捕获API | -| 编码器模式 | 3.4 | 旋钮值读取 | HAL编码器API | -| UART | 4.1 | 串口Echo、日志输出 | HAL_UART收发 | -| DMA | 4.2 | DMA发送/接收/循环缓冲 | HAL DMA API | -| SPI | 4.3 | SPI读写寄存器、设备探测 | HAL SPI API | -| I2C | 4.4 | I2C设备ID读取、传感器寄存器 | HAL I2C API | -| ADC | 5.1 | 电位器采样、电压读取 | HAL ADC API | -| Flash | 6.1 | 参数保存与恢复 | HAL Flash API | -| Watchdog | 6.2 | 看门狗复位演示 | HAL IWDG/WWDG API | -| 低功耗 | 6.3 | 低功耗休眠与唤醒 | HAL_PWR API | -| RTC与Backup | 6.4 | 断电后信息保留 | HAL RTC API | -| 中断模型 | 7.1 | 中断优先级实验 | NVIC相关API | -| C++23驱动封装模式 | 7.2 | 统一驱动风格模板 | - | -| 事件驱动与状态机 | 7.3 | 事件循环、状态机示例 | - | -| 串口命令行CLI | 8.1 | 串口命令控制 | HAL_UART + 自实现 | -| 模拟量采集器 | 8.2 | ADC采样、数据打印 | HAL ADC + 自实现 | -| 参数保存系统 | 8.3 | Flash参数持久化 | HAL Flash + 自实现 | -| 低功耗唤醒演示 | 8.4 | 按键唤醒与休眠 | HAL_PWR + EXTI | - -### 1.2 C++23特性索引 - -| C++23特性 | 首次引入章节 | -|----------|-------------| -| `std::expected` | 1.2 | -| `std::span` | 1.2 | -| `std::array` | 1.2 | -| `std::byte` | 1.2 | -| `std::to_underlying` | 1.2 | -| `constexpr` | 1.3 | -| `consteval` | 2.1 | -| `if consteval` | 2.1 | -| `enum class` | 2.2 | -| `[[nodiscard]]` | 2.2 | -| `std::byteswap` | 4.3 | -| `deducing this` | 7.3 | -| 静态lambda | 7.2 | - ---- - -## 2. 章节总览 - -### 第1章:工程准备 - -- 工具链安装与验证 -- 交叉编译基础 -- CMake 工程骨架 -- 启动文件与链接脚本 -- HAL/CMSIS 的角色划分 -- freestanding 约束说明 - -### 第2章:时钟与基础I/O - -- 时钟树 -- GPIO -- EXTI -- SysTick - -### 第3章:定时器类外设 - -- 基础 TIM -- PWM TIM -- 输入捕获 TIM -- 编码器 TIM - -### 第4章:通信外设 - -- UART -- DMA -- SPI -- I2C - -### 第5章:模拟与数据采集 - -- ADC - -### 第6章:系统可靠性与存储 - -- Flash -- Watchdog -- 低功耗 -- RTC / Backup - -### 第7章:中断与架构 - -- 中断模型 -- C++23 驱动封装模式 -- 事件驱动与状态机 - -### 第8章:综合项目 - -- 串口命令行 -- 传感器采集 -- 参数保存 -- 低功耗唤醒 -- 简易控制台 - ---- - -## 3. 第1章:工程准备 - -### 3.1:工具链与构建系统 - -### 目标 - -让读者完成交叉编译环境的基本搭建,并理解"为什么需要独立工具链和构建系统"。 - -### 内容 - -- arm-none-eabi-gcc 的作用 -- binutils、objdump、size 的作用 -- CMake 的交叉编译配置 -- VS Code 的任务和调试配置 -- 编译产物:`.elf`、`.bin`、`.hex` -- 烧录与下载方式 - -### 产出 - -- 能够一键编译 -- 能够生成可下载固件 -- 能够在板子上跑通最小程序 - -### C++23 关联 - -- 无需引入复杂语言特性,重点是建立"可编译的现代 C++ 工程基础" -- 建议开启:`-std=c++23` -- 建议关闭:异常、RTTI(按需) - -### 验收 - -- 工程成功编译 -- 输出文件可被下载 -- 板子能进入最小可运行状态 - ---- - -### 3.2:freestanding 策略 - -### 目标 - -告诉读者在裸机环境下,哪些 C++ 用法应当优先,哪些应当谨慎。 - -### 内容 - -- freestanding 与 hosted 的区别 -- 为什么裸机教程不建议一开始就依赖完整标准库生态 -- 固定容量容器优先 -- 运行时分配最小化 -- 错误处理优先返回值 / `std::expected` - -### 建议输出规范 - -- 主路径不依赖 `new/delete` -- 主路径不依赖异常 -- 主路径不依赖 RTTI -- 主路径不依赖 iostream - -### C++23 关联 - -- `std::expected` -- `std::span` -- `std::array` -- `std::byte` -- `std::to_underlying` -- `constexpr` / `consteval` / `if consteval` - -### 验收 - -- 读者能够明确:哪些库在教程中是"默认可用",哪些是"可选增强" -- 读者能够理解为什么这样设计更适合 STM32F103 - ---- - -### 3.3:启动文件与链接脚本 - -### 目标 - -让读者知道程序为什么能从 Flash 跑起来,以及启动阶段发生了什么。 - -### 内容 - -- 启动文件 `startup.s` -- 向量表 -- `Reset_Handler` -- 数据段、BSS 段初始化 -- 链接脚本中的 Flash / RAM 布局 -- 栈与堆的概念 - -### C++23 关联 - -- 无直接语言特性为主,重点是"编译期配置"思想 -- 可用 `constexpr` 定义内存布局常量 - -### 验收 - -- 读者能解释 `main()` 之前的流程 -- 读者知道为什么 `.data` 需要搬运、`.bss` 需要清零 -- 读者知道链接脚本为什么不是可有可无 - ---- - -### 3.4:最小 BSP 与板级抽象 - -### 目标 - -建立"板级差异收口"的基本架构。 - -### 内容 - -- `board/` 目录职责 -- Blue Pill 的板级资源分配 -- LED / 按键 / 串口 / 调试口的命名规范 -- 板级初始化入口 - -### C++23 关联 - -- `enum class` 用于硬件资源枚举 -- `std::to_underlying` 用于枚举索引和表驱动结构 -- `[[nodiscard]]` 标记初始化结果 - -### 验收 - -- 板级资源定义集中管理 -- 上层应用不直接关心 pin 号细节 - ---- - -## 4. 第2章:时钟与基础I/O - -### 4.1:时钟树 - -### 目标 - -让读者理解 STM32F103 的时钟系统如何工作。 - -### 内容 - -- HSI / HSE / PLL -- AHB / APB1 / APB2 -- 系统主频与外设时钟 -- 为什么定时器时钟有"倍频规则" -- HAL 时钟配置入口 - -### 产出 - -- 一个稳定的系统时钟配置 -- 一个可重复说明的时钟树图 - -### C++23 关联 - -- `constexpr` 用于描述时钟参数 -- `consteval` 可用于生成固定配置 -- `if consteval` 可用于编译期/运行期分支 -- `std::to_underlying` 可用于时钟源枚举转换 - -### 验收 - -- 读者能说清 `SYSCLK`、`HCLK`、`PCLK1`、`PCLK2` -- 读者知道改频率会影响什么 - ---- - -### 4.2:GPIO - -### 目标 - -独立讲清 GPIO 的工作方式,并让读者完成第一条真正的外设驱动。 - -### 内容 - -- 输入 / 输出 / 复用 / 模拟模式 -- 推挽 / 开漏 -- 上拉 / 下拉 -- GPIO 速度 -- 端口与引脚概念 -- HAL GPIO 初始化与读写 - -### 产出 - -- LED 闪烁 -- 按键读取 -- 简单输出控制 - -### C++23 关联 - -- 模板参数固定端口与引脚 -- `constexpr` pin 配置 -- `enum class` 表达模式 -- `[[nodiscard]]` 表达初始化结果 -- `std::to_underlying` 表达枚举底层值 - -### 建议封装形式 - -- `GpioPin` -- `GpioOutput` -- `GpioInput` - -### 验收 - -- 读者能够独立配置一个 GPIO 引脚 -- 读者能够解释推挽与开漏的差异 -- 读者能够用 GPIO 做 LED 与按键实验 - ---- - -### 4.3:EXTI - -### 目标 - -只讲外部中断与事件触发,不混入定时器内容。 - -### 内容 - -- EXTI 触发源 -- 上升沿 / 下降沿 -- 中断与事件的区别 -- 按键中断 -- 中断里应做什么,不应做什么 -- 消抖策略 - -### 产出 - -- 按键中断点亮 / 熄灭 LED -- 中断触发日志输出 - -### C++23 关联 - -- `std::expected` 用于中断注册、线路绑定结果 -- `constexpr` 用于中断线路映射 -- 静态 lambda / 无捕获回调用于 ISR 转发 - -### 验收 - -- 读者知道 EXTI 的触发条件 -- 读者知道中断服务函数和主循环分工 -- 读者能独立实现按键中断 - ---- - -### 4.4:SysTick - -### 目标 - -只讲系统节拍器与基础时间基准。 - -### 内容 - -- SysTick 是什么 -- 1ms tick 的建立 -- 延时函数的实现思路 -- 非阻塞计时的基础 -- `HAL_GetTick` 的作用与限制 - -### 产出 - -- 固定节拍的时间基准 -- 基于 tick 的简单任务调度雏形 - -### C++23 关联 - -- `constexpr` 时间常量 -- `std::chrono` 可作为"可选增强"而非核心依赖 -- `std::expected` 可用于 tick 服务初始化 - -### 验收 - -- 读者能使用节拍做周期任务 -- 读者能理解阻塞延时与非阻塞延时的差别 - ---- - -## 5. 第3章:定时器类外设 - -### 5.1:基础 TIM - -### 目标 - -只讲通用定时器的最基础用途:计数、定时、中断。 - -### 内容 - -- 计数器 -- 预分频器 -- 自动重装载 -- 更新中断 -- 定时器启动与停止 - -### 产出 - -- 周期定时中断 -- 软件节拍器 - -### C++23 关联 - -- `constexpr` 计算预分频与重装载 -- `std::expected` 返回定时器初始化结果 -- `enum class` 表示定时器状态 - -### 验收 - -- 读者能配置一个基础定时器 -- 读者知道定时器计数和中断如何发生 - ---- - -### 5.2:PWM - -### 目标 - -只讲 PWM 输出,不混入输入捕获、编码器等其他模式。 - -### 内容 - -- PWM 基本概念 -- 占空比 -- 频率 -- 通道 -- 极性 -- HAL PWM 输出 - -### 产出 - -- LED 呼吸灯 -- 蜂鸣器控制 -- 简单电机/舵机输出基础 - -### C++23 关联 - -- `constexpr` 计算 PWM 参数 -- 模板固定通道与频率 -- `std::expected` 反馈配置错误 - -### 验收 - -- 读者能够让一个通道输出 PWM -- 读者能够调节占空比与频率 - ---- - -### 5.3:输入捕获 - -### 目标 - -只讲 TIM 输入捕获模式。 - -### 内容 - -- 上升沿 / 下降沿捕获 -- 计数值读取 -- 周期测量 -- 频率测量 -- 脉宽测量 - -### 产出 - -- 测频器 -- 测脉宽器 - -### C++23 关联 - -- `std::expected` 作为测量结果返回方式 -- `std::span` 用于缓存多次测量值 -- `constexpr` 用于阈值和换算参数 - -### 验收 - -- 读者能够测量一个外部信号周期 -- 读者能够解释输入捕获与外部中断的差别 - ---- - -### 5.4:编码器模式 - -### 目标 - -只讲定时器编码器接口模式。 - -### 内容 - -- A/B 相编码器原理 -- 计数方向 -- 速度与位移计算 -- 机械编码器与光电编码器差异 - -### 产出 - -- 旋钮值读取 -- 计数器随编码器转动变化 - -### C++23 关联 - -- `constexpr` 定义编码器换算因子 -- `std::expected` 用于初始化结果 - -### 验收 - -- 读者能够读取编码器计数 -- 读者能够进行基础位置/速度计算 - ---- - -## 6. 第4章:通信外设 - -### 6.1:UART - -### 目标 - -只讲串口基础收发与常见工程用法。 - -### 内容 - -- UART 波特率 -- 起始位 / 数据位 / 校验位 / 停止位 -- 阻塞收发 -- 中断收发 -- 基本调试输出 - -### 产出 - -- 串口 Echo -- 串口日志输出 - -### C++23 关联 - -- `std::span` 用于发送缓冲 -- `std::expected` 用于发送/接收结果 -- `std::byte` 用于原始字节数据 -- `std::to_underlying` 用于状态码 - -### 验收 - -- 读者能够发送字符串与原始数据 -- 读者能够收发调试信息 - ---- - -### 6.2:DMA - -### 目标 - -只讲 DMA 的基本搬运能力,不掺入具体外设应用。 - -### 内容 - -- DMA 的职责 -- 外设到内存、内存到外设、内存到内存 -- 传输完成中断 -- 半传输中断 -- 环形模式 -- 双缓冲思路(如果适用) - -### 产出 - -- DMA 发送 -- DMA 接收 -- DMA 循环缓冲 - -### C++23 关联 - -- `std::span` 表示 DMA 缓冲区 -- `std::array` 作为固定缓冲区载体 -- `std::expected` 表达配置结果 - -### 验收 - -- 读者知道 DMA 为什么能减轻 CPU 负担 -- 读者能够把一个 DMA 通道跑起来 - ---- - -### 6.3:SPI - -### 目标 - -只讲 SPI 通信接口。 - -### 内容 - -- SCK / MOSI / MISO / NSS -- 主机 / 从机 -- CPOL / CPHA -- HAL SPI 发送接收 -- 常见时序问题 - -### 产出 - -- SPI 读写寄存器 -- SPI 设备探测 - -### C++23 关联 - -- `std::span` 表示收发数据块 -- `std::expected` 表示设备探测结果 -- `std::byteswap` 用于多字节数据处理 - -### 验收 - -- 读者能够和一个 SPI 外设完成通信 -- 读者能够解释 SPI 模式配置 - ---- - -### 6.4:I2C - -### 目标 - -只讲 I2C 通信接口。 - -### 内容 - -- SCL / SDA -- 开漏与上拉 -- 地址机制 -- ACK / NACK -- 起始 / 停止条件 -- HAL I2C 读写 - -### 产出 - -- 读取 I2C 设备 ID -- 读取传感器寄存器 - -### C++23 关联 - -- `std::span` 用于数据缓冲 -- `std::expected` 用于通讯结果 -- `std::to_underlying` 用于寄存器地址表达 - -### 验收 - -- 读者能够完成一次 I2C 读写 -- 读者能够解释为什么 I2C 需要上拉 - ---- - -## 7. 第5章:模拟与数据采集 - -### 7.1:ADC - -### 目标 - -只讲 ADC 的采样机制与数据读取。 - -### 内容 - -- 模拟输入 -- 分辨率 -- 采样时间 -- 单次转换 / 连续转换 -- 通道扫描 -- 校准思路 - -### 产出 - -- 电位器采样 -- 电压读取 -- 简单模拟量展示 - -### C++23 关联 - -- `std::expected` 返回采样初始化结果 -- `std::span` 用于采样缓冲 -- `constexpr` 用于量化参数与换算公式 - -### 验收 - -- 读者能够读取模拟电压值 -- 读者能够解释 ADC 原始值与工程值的关系 - ---- - -## 8. 第6章:系统可靠性与存储 - -### 8.1:Flash - -### 目标 - -只讲片上 Flash 的读取、擦除、写入与参数保存。 - -### 内容 - -- Flash 结构 -- 页擦除 -- 半字/字写入限制 -- 参数区设计 -- 校验与版本号 - -### 产出 - -- 参数保存与恢复 -- 重启后参数保留 - -### C++23 关联 - -- `std::span` 表示待写数据 -- `std::expected` 表示 Flash 操作结果 -- `std::array` 作为参数块载体 -- `std::byteswap` 用于持久化数据格式处理 - -### 验收 - -- 读者能够把配置写进 Flash -- 读者能够在复位后恢复配置 - ---- - -### 8.2:Watchdog - -### 目标 - -只讲独立看门狗与窗口看门狗的基础使用。 - -### 内容 - -- IWDG -- WWDG -- 喂狗机制 -- 故障恢复 -- 程序卡死后的自动复位 - -### 产出 - -- 看门狗复位演示 -- 故障恢复演示 - -### C++23 关联 - -- `std::expected` 用于喂狗前的状态检查 -- `constexpr` 用于喂狗周期配置 - -### 验收 - -- 读者能够解释看门狗用途 -- 读者能够演示超时复位 - ---- - -### 8.3:低功耗 - -### 目标 - -只讲低功耗模式本身,不混入 RTC 细节。 - -### 内容 - -- Sleep / Stop / Standby -- 唤醒来源 -- 进入与退出低功耗流程 -- 功耗与响应速度的权衡 - -### 产出 - -- 低功耗休眠演示 -- 外部中断唤醒演示 - -### C++23 关联 - -- `constexpr` 用于模式配置 -- `std::expected` 用于模式切换结果 - -### 验收 - -- 读者知道三种低功耗模式的差异 -- 读者能够让板子进入并退出低功耗 - ---- - -### 8.4:RTC 与 Backup - -### 目标 - -只讲 RTC 与备份域,不混入一般低功耗章节。 - -### 内容 - -- RTC 基本用途 -- 备份寄存器 -- 断电保持数据的设计思路 -- 时间戳、唤醒计数、状态恢复 - -### 产出 - -- 断电后信息保留 -- 简单时间记录 - -### C++23 关联 - -- `std::expected` 表达访问结果 -- `std::array` 存储备份数据模板 -- `constexpr` 定义固定布局 - -### 验收 - -- 读者知道备份域可以保存什么 -- 读者能够把最小状态保存到备份区 - ---- - -## 9. 第7章:中断与架构 - -### 9.1:中断模型 - -### 目标 - -只讲中断机制本身,不讲具体外设。 - -### 内容 - -- NVIC -- 中断优先级 -- 局部屏蔽与全局屏蔽 -- ISR 的职责边界 -- 中断安全与临界区 - -### 产出 - -- 中断优先级实验 -- 简单中断嵌套观察 - -### C++23 关联 - -- `constexpr` 定义优先级表 -- `std::expected` 统一外设中断注册返回 -- 静态回调映射 - -### 验收 - -- 读者能够解释为什么 ISR 里不适合做重活 -- 读者能够配置基本中断优先级 - ---- - -### 9.2:C++23 驱动封装模式 - -### 目标 - -把前面学的外设封装成可维护的 C++ 驱动风格。 - -### 内容 - -- `enum class` 表达状态与模式 -- `std::expected` 表达初始化和运行错误 -- `std::span` 表达缓冲区 -- `constexpr` 参数驱动 -- 模板固定硬件资源 -- 无捕获回调适配 ISR -- 轻量 RAII 初始化/释放 - -### 产出 - -- 一个统一的驱动风格模板 -- 一套可复用的 error/result 约定 - -### 建议说明的 C++23 特性 - -- `std::expected` -- `std::span` -- `std::byte` -- `std::to_underlying` -- `constexpr` -- `consteval` -- `if consteval` -- `deducing this` -- 静态 lambda -- `[[nodiscard]]` - -### 验收 - -- 读者能看懂"为什么这样封装更适合嵌入式" -- 读者能基于统一风格实现一个新外设驱动 - ---- - -### 9.3:事件驱动与状态机 - -### 目标 - -只讲事件驱动模型,不再展开具体外设。 - -### 内容 - -- 事件队列 -- 状态机 -- 生产者/消费者 -- 主循环与中断协作 -- 非阻塞设计 - -### 产出 - -- 一个简单事件循环 -- 一个状态机驱动示例 - -### C++23 关联 - -- `constexpr` 状态表 -- `std::span` 事件缓冲 -- `std::expected` 状态转移结果 -- `deducing this` 改善对象风格 API - -### 验收 - -- 读者能把"中断只投递事件,主循环负责处理"说清楚 -- 读者能写出一个非阻塞状态机 - ---- - -## 10. 第8章:综合项目 - -### 10.1:串口命令行 CLI - -### 目标 - -把 UART、命令解析、状态管理串起来,但仍保持重点是 UART 主线。 - -### 功能 - -- 输入命令 -- 查看帮助 -- 控制 LED -- 调整参数 -- 读取状态 - -### 建议实现 - -- 固定长度命令行缓冲 -- 轻量命令表 -- 只用 freestanding 友好的数据结构 - -### C++23 关联 - -- `std::span` -- `std::expected` -- `constexpr` 命令表 -- `std::to_underlying` - ---- - -### 10.2:模拟量采集器 - -### 目标 - -围绕 ADC 构建一个完整采集示例。 - -### 功能 - -- ADC 采样 -- 周期采样 -- 数据打印 -- 简单滤波 - -### C++23 关联 - -- `std::array` -- `std::span` -- `constexpr` -- `std::expected` - ---- - -### 10.3:参数保存系统 - -### 目标 - -围绕 Flash 构建参数持久化能力。 - -### 功能 - -- 读取配置 -- 修改配置 -- 写入 Flash -- 断电恢复 -- 配置版本兼容 - -### C++23 关联 - -- `std::array` -- `std::span` -- `std::byteswap` -- `std::expected` - ---- - -### 10.4:低功耗唤醒演示 - -### 目标 - -围绕低功耗与外部中断做完整案例。 - -### 功能 - -- 按键唤醒 -- 进入休眠 -- 退出后恢复状态 - -### C++23 关联 - -- `constexpr` -- `std::expected` - ---- - -## 11. 每章统一模板 - -为了让整套教程风格一致,建议每个章节都按下面模板写: - -## 章节标题 - -### 1)本章目标 - -一句话说明这一章要解决什么问题。 - -### 2)硬件原理 - -只讲这一章对应 feature 的硬件原理。 - -### 3)HAL 接口 - -只讲这一章对应 feature 的 HAL API。 - -### 4)最小 demo - -给出可以直接跑的最小例子。 - -### 5)C++23 封装 - -演示如何用现代 C++ 改善接口。 - -### 6)常见坑 - -列出最常见的问题。 - -### 7)练习题 - -让读者自己扩展。 - -### 8)可复用代码片段 - -沉淀成 `drivers/` 中的模块。 - -### 9)本章小结 - -总结本章唯一核心 feature。 - ---- - -## 12. C++23 feature 对照建议 - -下面是建议你在教程中逐步引入的 C++23 特性清单。 - -### 12.1 优先讲解 - -- `std::expected` -- `std::span` -- `std::array` -- `std::byte` -- `std::to_underlying` -- `constexpr` -- `consteval` -- `if consteval` -- `[[nodiscard]]` -- `enum class` - -### 12.2 进阶讲解 - -- `std::byteswap` -- `deducing this` -- 静态 lambda -- 静态 `operator()` -- `constexpr` 更深入的编译期策略 - -### 12.3 可选增强 - -- `std::move_only_function` -- `std::ranges` -- 更复杂的模板元编程 -- 更高级的概念约束 - ---- - -## 13. freestanding 使用约束建议 - -### 13.1 默认推荐 - -- 使用固定容量容器 -- 使用静态存储期对象 -- 使用编译期配置 -- 使用无捕获回调 -- 使用显式错误返回 - -### 13.2 默认谨慎 - -- `new/delete` -- `exception` -- `RTTI` -- `iostream` -- 过度依赖动态分配的容器与算法 - -### 13.3 可以作为可选扩展 - -- 某些 STL 算法 -- 某些轻量 `` 能力 -- 某些工具库能力 - -前提是它们不会破坏你教程的"可移植、可解释、可教学"目标。 - ---- - -## 14. 推荐的知识递进顺序 - -1. 工具链与最小工程 -2. 启动文件与链接脚本 -3. 时钟树 -4. GPIO -5. EXTI -6. SysTick -7. 基础 TIM -8. PWM -9. 输入捕获 -10. 编码器 -11. UART -12. DMA -13. SPI -14. I2C -15. ADC -16. Flash -17. Watchdog -18. 低功耗 -19. RTC / Backup -20. 中断模型 -21. C++23 封装模式 -22. 综合项目 - ---- - -## 15. 附录 - -### 15.1 附录A:详细硬件模块对照表 - -| 硬件模块 | 章节 | HAL API类型 | 实验产出 | -|---------|------|------------|---------| -| 工具链 | 1.1 | arm-none-eabi-gcc, CMake | 一键编译环境、可下载固件、最小程序运行 | -| freestanding策略 | 1.2 | - | 建立freestanding编码规范 | -| 启动文件与链接脚本 | 1.3 | - | 理解启动流程、数据段搬运、BSS清零 | -| 最小BSP与板级抽象 | 1.4 | - | 板级资源集中管理、LED/按键/串口命名规范 | -| 时钟树 | 2.1 | HAL时钟配置API | 系统时钟配置、时钟树图 | -| GPIO | 2.2 | HAL_GPIO初始化/读写 | LED闪烁、按键读取、简单输出控制 | -| EXTI | 2.3 | HAL EXTI相关API | 按键中断点亮/熄灭LED、中断日志输出 | -| SysTick | 2.4 | HAL_GetTick | 固定节拍时间基准、简单任务调度雏形 | -| 基础TIM | 3.1 | HAL定时器API | 周期定时中断、软件节拍器 | -| PWM | 3.2 | HAL PWM API | LED呼吸灯、蜂鸣器控制、简单电机/舵机输出 | -| 输入捕获 | 3.3 | HAL输入捕获API | 测频器、测脉宽器 | -| 编码器模式 | 3.4 | HAL编码器API | 旋钮值读取、计数器随转动变化 | -| UART | 4.1 | HAL_UART收发 | 串口Echo、串口日志输出 | -| DMA | 4.2 | HAL DMA API | DMA发送、DMA接收、DMA循环缓冲 | -| SPI | 4.3 | HAL SPI API | SPI读写寄存器、SPI设备探测 | -| I2C | 4.4 | HAL I2C API | I2C设备ID读取、传感器寄存器读取 | -| ADC | 5.1 | HAL ADC API | 电位器采样、电压读取、模拟量展示 | -| Flash | 6.1 | HAL Flash API | 参数保存与恢复、重启后参数保留 | -| Watchdog | 6.2 | HAL IWDG/WWDG API | 看门狗复位演示、故障恢复演示 | -| 低功耗 | 6.3 | HAL_PWR API | 低功耗休眠演示、外部中断唤醒 | -| RTC与Backup | 6.4 | HAL RTC API | 断电后信息保留、简单时间记录 | -| 中断模型 | 7.1 | NVIC相关API | 中断优先级实验、中断嵌套观察 | -| C++23驱动封装模式 | 7.2 | - | 统一驱动风格模板、error/result约定 | -| 事件驱动与状态机 | 7.3 | - | 事件循环、状态机驱动示例 | -| 串口命令行CLI | 8.1 | HAL_UART + 自实现 | 命令输入、帮助、LED控制、参数调整、状态读取 | -| 模拟量采集器 | 8.2 | HAL ADC + 自实现 | ADC采样、周期采样、数据打印、简单滤波 | -| 参数保存系统 | 8.3 | HAL Flash + 自实现 | 配置读写、Flash写入、断电恢复、版本兼容 | -| 低功耗唤醒演示 | 8.4 | HAL_PWR + EXTI | 按键唤醒、休眠进入、状态恢复 | - -### 15.2 附录B:C++23特性章节对照 - -#### 3. 第1章:工程准备 -- **1.2 freestanding策略**:`std::expected`, `std::span`, `std::array`, `std::byte`, `std::to_underlying`, `constexpr`, `consteval`, `if consteval` -- **1.3 启动文件与链接脚本**:`constexpr` -- **1.4 最小BSP与板级抽象**:`enum class`, `std::to_underlying`, `[[nodiscard]]` - -#### 4. 第2章:时钟与基础I/O -- **2.1 时钟树**:`constexpr`, `consteval`, `if consteval`, `std::to_underlying` -- **2.2 GPIO**:模板参数, `constexpr`, `enum class`, `[[nodiscard]]`, `std::to_underlying` -- **2.3 EXTI**:`std::expected`, `constexpr`, 静态lambda -- **2.4 SysTick**:`constexpr`, `std::expected` - -#### 5. 第3章:定时器类外设 -- **3.1 基础TIM**:`constexpr`, `std::expected`, `enum class` -- **3.2 PWM**:`constexpr`, 模板参数, `std::expected` -- **3.3 输入捕获**:`std::expected`, `std::span`, `constexpr` -- **3.4 编码器模式**:`constexpr`, `std::expected` - -#### 6. 第4章:通信外设 -- **4.1 UART**:`std::span`, `std::expected`, `std::byte`, `std::to_underlying` -- **4.2 DMA**:`std::span`, `std::array`, `std::expected` -- **4.3 SPI**:`std::span`, `std::expected`, `std::byteswap` -- **4.4 I2C**:`std::span`, `std::expected`, `std::to_underlying` - -#### 7. 第5章:模拟与数据采集 -- **5.1 ADC**:`std::expected`, `std::span`, `constexpr` - -#### 8. 第6章:系统可靠性与存储 -- **6.1 Flash**:`std::span`, `std::expected`, `std::array`, `std::byteswap` -- **6.2 Watchdog**:`std::expected`, `constexpr` -- **6.3 低功耗**:`constexpr`, `std::expected` -- **6.4 RTC与Backup**:`std::expected`, `std::array`, `constexpr` - -#### 9. 第7章:中断与架构 -- **7.1 中断模型**:`constexpr`, `std::expected`, 静态回调映射 -- **7.2 C++23驱动封装模式**:`enum class`, `std::expected`, `std::span`, `std::byte`, `std::to_underlying`, `constexpr`, `consteval`, `if consteval`, `deducing this`, 静态lambda, `[[nodiscard]]` -- **7.3 事件驱动与状态机**:`constexpr`, `std::span`, `std::expected`, `deducing this` - -#### 10. 第8章:综合项目 -- **8.1 串口命令行CLI**:`std::span`, `std::expected`, `constexpr`, `std::to_underlying` -- **8.2 模拟量采集器**:`std::array`, `std::span`, `constexpr`, `std::expected` -- **8.3 参数保存系统**:`std::array`, `std::span`, `std::byteswap`, `std::expected` -- **8.4 低功耗唤醒演示**:`constexpr`, `std::expected` diff --git a/drafts/Content-Table-Draft.md b/drafts/Content-Table-Draft.md deleted file mode 100644 index 8d72fc21d..000000000 --- a/drafts/Content-Table-Draft.md +++ /dev/null @@ -1,566 +0,0 @@ -#### 第15章 高性能数据结构与算法(嵌入式角度) - -- 紧凑与缓存友好的数据结构(结构体内存布局、padding elimination) -- 预分配表驱动状态机、跳表、紧凑哈希/开地址哈希 -- 查表 vs 计算:什么时候用 LUT,何时用公式 -- 练习:优化一个事件分发器(从链表到数组/位掩码实现) - ------- - -#### 第16章 模式与范式(嵌入式友好的现代 C++ 设计) - -- 静态多态(CRTP)与零开销抽象 -- 非虚拟接口 (NVI) 与静态多态替代虚函数 -- 策略模式(policy-based design)、可插拔组件 -- 责任链、命令、状态机在固件架构中的落地 -- 练习:使用 CRTP 重写驱动抽象层并比较开销 - ------- - -#### 第17章 性能分析与剖析(如何找到真正的瓶颈) - -- 仪器化 vs 采样分析(SWO、ITM、SEGGER RTT、gprof、perf) -- 基于周期计数的微基准(`DWT->CYCCNT`、PMU) -- 二进制大小分析(`arm-none-eabi-size`、readelf、objdump) -- 案例:定位一次高延迟问题的完整流程 -- 练习:为一个功能写 microbench 并解释结果(避免误导性基准) - ------- - -#### 第18章 编译器与优化技巧(深入理解生成代码) - -- inline、force_inline、函数边界的成本 -- 寄存器使用、栈帧、尾调用优化 -- 代码对齐与分支预测友好编写(避免分支错判的微优化) -- 源码到汇编:读懂关键函数的汇编输出并优化 -- 练习:对比三种实现(循环展开、递归、手写汇编),分析生成代码 - - - -## **第五部分:嵌入式专题:硬件交互与系统编程** - -#### 第19章 能耗优化(面向电池供电设备) - -- 省电模式的 API 设计(睡眠/唤醒、外设休眠) -- 代码与硬件配合减少唤醒次数(批量传输、采样策略) -- 编译与链接对能耗的间接影响(代码大小、缓存命中) -- 练习:实现低功耗传感器节点并测量平均电流(理论)估算 - ------- - -#### 第21章 网络、序列化与数据处理(边缘计算视角) - -- 轻量协议选择(MQTT-SN、CoAP、CBOR、protobuf-lite) -- 内存与零拷贝序列化技巧 -- 流式处理与管道化设计(减少内存复制) -- 练习:实现一个零拷贝的消息传输层(SPI/串口 + CBOR) - -#### 第14章 寄存器与外设访问 - -- 14.1 易失性(volatile)的正确使用 -- 14.2 寄存器映射的C++封装 -- 14.3 位域(Bit Field)与位操作 -- 14.4 类型安全的寄存器类设计 -- 14.5 编译期寄存器地址验证 -- 14.6 MMIO(内存映射IO)最佳实践 -- 14.7 原子位操作 - -#### 第15章 中断与异常处理 - -- 15.1 C++中的中断服务例程(ISR) - - 在 C++ 中编写安全的 ISR:可重入函数、最小工作量、ISR-safe API - - 与对象生命周期的交互(从 ISR 触发任务/信号的最佳实践) - - 将复杂逻辑移出 ISR(Deferred work、task notifications) - - 练习:设计一个 ISR + Deferred Handler 流水线(低延迟与低抖动) -- 15.2 extern "C"与链接规范 -- 15.3 中断向量表配置 -- 15.4 C++异常处理的代价 -- 15.5 无异常C++编程(-fno-exceptions) -- 15.6 错误码 vs 异常 -- 15.7 Result类型模式 - -#### 第16章 驱动程序开发 - -- 16.1 HAL层抽象设计 -- 16.2 GPIO驱动封装 -- 16.3 SPI/I2C/UART通信类 -- 16.4 DMA操作的C++接口 -- 16.5 定时器与PWM控制 -- 16.6 ADC/DAC数据采集 -- 16.7 驱动程序的测试策略 - ------- - -### **第六部分:设计模式与架构** - -#### 第17章 嵌入式设计模式 - -- 17.1 状态机模式的现代实现 -- 17.2 观察者模式与事件系统 -- 17.3 策略模式:编译期多态实现 -- 17.4 单例模式的线程安全实现 -- 17.5 工厂模式与对象创建 -- 17.6 命令模式与任务队列 -- 17.7 代理模式与硬件抽象 - -#### 第18章 模块化与接口设计 - -- 18.1 面向接口编程 -- 18.2 依赖注入在嵌入式中的应用 -- 18.3 模块化通信协议设计 -- 18.4 组件化架构 -- 18.5 C++20 Modules初探 -- 18.6 头文件组织与编译优化 -- 18.7 ABI稳定性考虑 - -#### 第19章 实时系统编程 - -- 19.1 确定性执行与时间约束 -- 19.2 优先级反转问题 -- 19.3 实时调度策略 -- 19.4 看门狗定时器使用 -- 19.5 时间管理与chrono库 -- 19.6 周期性任务实现 -- 19.7 抖动(Jitter)控制 - ------- - -### **第七部分:调试、测试与工具** - -#### 第20章 性能分析与优化 - -- 20.1 代码大小优化技术 -- 20.2 执行速度优化策略 -- 20.3 编译器优化选项详解 -- 20.4 链接时优化(LTO) -- 20.5 性能分析工具使用 -- 20.6 热点代码识别 -- 20.7 汇编代码阅读与优化 - -#### 第21章 调试技术 - -- 21.1 断言(assert)与静态断言 -- 21.2 调试宏与条件编译 -- 21.3 JTAG/SWD调试 -- 21.4 日志系统设计 -- 21.5 printf调试的替代方案 -- 21.6 内存越界检测 -- 21.7 栈溢出监控 - -#### 第22章 单元测试与持续集成 - -- 22.1 嵌入式系统的测试策略 -- 22.2 Google Test在嵌入式中的应用 -- 22.3 Mock对象与硬件模拟 -- 22.4 主机端测试(Host-based Testing) -- 22.5 静态分析工具(Clang-Tidy、Cppcheck) -- 22.6 代码覆盖率分析 -- 22.7 CI/CD流程搭建 - ------- - -### **第八部分:嵌入式Linux应用开发** - -#### 第23章 Linux系统编程基础 - -- 23.1 文件IO与文件描述符 -- 23.2 sysfs与设备文件访问 -- 23.3 ioctl系统调用 -- 23.4 mmap内存映射 -- 23.5 信号处理 -- 23.6 进程间通信(IPC) -- 23.7 多线程编程(std::thread) - -#### 第24章 Linux设备交互 - -- 24.1 GPIO通过sysfs/libgpiod操作 -- 24.2 I2C设备访问 -- 24.3 SPI设备通信 -- 24.4 串口编程 -- 24.5 网络socket编程 -- 24.6 USB设备交互 -- 24.7 V4L2视频采集 - -#### 第25章 系统服务与守护进程 - -- 25.1 守护进程(Daemon)编写 -- 25.2 systemd服务集成 -- 25.3 D-Bus通信 -- 25.4 日志记录(syslog) -- 25.5 配置文件解析 -- 25.6 权限管理与安全 -- 25.7 系统资源监控 - ------- - -### **第九部分:高级主题** - -#### 第26章 协程与异步编程 - -- 26.1 C++20协程(Coroutines)基础 -- 26.2 异步IO模式 -- 26.3 生成器(Generator)实现 -- 26.4 任务调度器设计 -- 26.5 协程在状态机中的应用 -- 26.6 协程的性能考虑 -- 26.7 async/await模式 - -#### 第27章 功耗管理 - -- 27.1 低功耗模式控制 -- 27.2 动态电压频率调节(DVFS) -- 27.3 外设电源管理 -- 27.4 睡眠模式与唤醒机制 -- 27.5 代码优化与功耗关系 -- 27.6 电池管理策略 -- 27.7 能耗测量方法 - -#### 第28章 安全编程实践 - -- 28.1 缓冲区溢出防护 -- 28.2 整数溢出检查 -- 28.3 安全的字符串操作 -- 28.4 加密库集成 -- 28.5 安全启动(Secure Boot) -- 28.6 固件更新OTA -- 28.7 代码签名与验证 - ------- - -## 附录 A:常用设计模板与代码片段(可直接拷贝) - -- 固定池、ring-buffer、DMA wrapper、外设 RAII 模板、ISR-safe queue -- CMake Cross-build 模板、链接脚本示例 - -## 附录 B:调试与测量工具清单(命令与示例) - -- GDB/LLDB、OpenOCD、J-Link、SEGGER RTT、perf、valgrind(对 Linux)、QEMU 启动示例 - -## 附录 C:术语表、性能陷阱清单、常见错误与对策 - -## 附录 D:建议阅读与参考资料(书籍、博客、标准文档、工具链手册 - -#### 附录A:常用工具链 - -- A.1 GCC/G++编译器 -- A.2 Clang/LLVM -- A.3 CMake构建系统 -- A.4 GDB调试器 -- A.5 OpenOCD -- A.6 性能分析工具 - -#### 附录B:标准库参考 - -- B.1 嵌入式可用的STL组件 -- B.2 头文件依赖关系 -- B.3 各编译器的支持情况 -- B.4 第三方嵌入式库推荐 - -#### 附录C:编码规范 - -- C.1 命名约定 -- C.2 代码风格 -- C.3 注释规范 -- C.4 MISRA C++规则精选 - -#### 附录D:常见问题与解决方案 - -- D.1 编译错误排查 -- D.2 链接问题解决 -- D.3 性能瓶颈诊断 -- D.4 内存问题调试 - - - - - ------- - -# 第一部分:基础篇 — 嵌入式 C++ 概览与设计思维 - -## 第1章 设计与约束 —— 嵌入式系统中写 C++ 的思维模型 - -- 嵌入式的资源与实时约束(Flash/ROM、RAM、CPU、功耗、启动时间、确定性) -- 嵌入式环境的特殊性:资源受限、实时性、可靠性 -- 语言选择原则:何时用 C++、用哪些 C++ 特性(折中与禁用项) -- 性能 vs 可维护性的真实取舍 -- 编码规范建议(小型嵌入式友好的 C++ 子集/风格指南 / MISRA C++ 精选) -- 练习:评估三个小示例(用 C、用 C++(面向对象),比较代码大小与性能) -- 章末速查:适用/禁用的 C++ 特性清单 - -## 第2章 嵌入式 C++ 入门与第一个工程 - -- 第一个嵌入式C++程序:LED 闪烁示例(裸机/RTOS/Linux 三种实现) -- extern "C" 与链接规范 -- 工具链配置简介(交叉编译基础) - -## 第3章 内存模型与布局 / 链接器脚本 - -- 嵌入式系统的内存架构(Flash、RAM、EEPROM) -- C++ 对象的内存布局 -- 栈与堆的使用策略 -- 静态存储区与常量数据 -- 链接器脚本(Memory、FLASH/RAM 布局、.bss/.data/.rodata) -- 代码段、数据段分析工具的使用(readelf、objdump、arm-none-eabi-size) - ------- - -# 第二部分:工具链、构建与编译期技术(为性能调优打基础) - -## 第4章 工具链与构建(为性能调优打基础) - -- 交叉编译基础与 CMake 多目标构建 -- 常用编译器选项(-O0/-Og/-O2/-Os/-Ofast、-fno-exceptions、-fno-rtti、-ffunction-sections/ -fdata-sections、-Wl,--gc-sections) -- 链接器与 LTO/ThinLTO,影响代码大小与性能的链接策略 -- 链接脚本示例(Memory map)与初始化顺序说明 -- 启动代码与初始化顺序(全局构造、构造开销、constinit/consteval 问题) -- 练习:建立最小 cross-build pipeline + 分析二进制节大小 - -## 第5章 编译期技术 —— 把工作尽量移到编译时 - -- `constexpr`(C++11/14/17/20)与设计技巧:常量表达式计算、lookup tables、编译期字符串处理 -- `consteval`、`constinit`(C++20) -- 模板元编程(TMP)基础与实用模式(编译期映射、选择实现) -- `static_assert`、SFINAE、`if constexpr` -- 类型萃取(Type Traits)与概念(Concepts,C++20) -- 编译期查找表/CRC 表/小型 FSM 的实现练习(运行时 vs 编译期比较) - ------- - -# 第三部分:类型、容器与内存管理(嵌入式的内存控制武器库) - -## 第6章 现代类型与轻量容器 - -- `std::span`、`std::string_view`、`string_ref` 的使用场景与零拷贝设计 -- `std::array`、`std::optional`、`std::variant` 在嵌入式的适用性与成本 -- small-buffer-optimization 思路(small-vector)、small_vector 实现练习 -- `std::pmr` / 自定义分配器(内嵌池、紧凑分配)——何时使用,何时避免 -- STL 容器在嵌入式中的性能分析与替代(ETL 等) -- 练习:实现一个固定容量的 `small_vector` 并在 ISR/线程间测试 - -## 第7章 容器与数据结构 — 高性能设计 - -- 紧凑与缓存友好的数据结构(结构体内存布局、padding elimination) -- 循环缓冲区(ring-buffer)实现与分析 -- 侵入式容器设计 -- 紧凑哈希/开地址哈希、表驱动状态机 -- 练习:优化一个事件分发器(从链表到数组/位掩码实现) - -## 第8章 内存管理与性能(嵌入式黄金法则) - -- 内存布局(静态、堆、栈)、碎片化与内存对齐 -- 固定池 / slab / arena 分配器实现与比较 -- 禁用 heap 或限制 heap 时的替代策略:对象池、栈分配、placement new -- 避免动态分配的 API 设计模式 -- 练习:实现并 benchmark 一个固定大小对象池(含多线程安全版本) - ------- - -# 第四部分:所有权、资源管理与错误处理 - -## 第9章 所有权与资源管理 —— RAII 实战 - -- RAII 在驱动/外设管理中的应用(GPIO、SPI、DMA、文件句柄) -- `unique_ptr`、`shared_ptr` 的嵌入式取舍(内存成本、控制周期) -- intrusive 智能指针与引用计数(非堆实现) -- 自定义删除器(Custom Deleter)与 Scope Guard 模式 -- 练习:用 RAII 封装外设初始化/关闭流程(支持异常与无异常两套策略) - -## 第10章 异常、RTTI 与错误处理策略 - -- 为什么在多数嵌入式场景下禁用异常(或限定使用) -- RTTI 的成本与替代(静态多态、CRTP) -- 替代方案:`expected`/`outcome`/错误码、`std::optional`、结果类型封装、Result 模式 -- 练习:把一个使用异常的库改造为返回 `Result` 的风格 - ------- - -# 第五部分:并发、同步与实时性 - -## 第11章 并发、原子与内存模型(从裸机到 Linux) - -- C++ 内存模型简介(顺序一致性、释放/获取语义) -- `std::atomic`、原子操作、内存栅栏在嵌入式的使用 -- 内存序(Memory Order)详解 -- Lock-free 与无阻塞数据结构(适用场景、ABA 问题) -- 练习:实现一个 lock-free 单生产者单消费者环形缓冲区并测试延迟 - -## 第12章 线程、RTOS 与任务间通信 - -- 轻量同步:自旋锁、信号量、条件变量的代价(RTOS vs Linux) -- RTOS 任务间通信与优先级设计 -- 中断安全的数据结构与优先级反转、看门狗使用 -- 练习:RTOS 下线程安全数据结构实现 - ------- - -# 第六部分:中断、ISR 与硬件交互 - -## 第13章 中断与 ISR 设计(关键的实时性能点) - -- 在 C++ 中编写安全的 ISR:可重入函数、最小工作量、ISR-safe API -- ISR 与对象生命周期的交互(从 ISR 触发任务/信号的最佳实践) -- 将复杂逻辑移出 ISR(Deferred work、task notifications) -- 练习:设计一个 ISR + Deferred Handler 流水线(低延迟与低抖动) - -## 第14章 寄存器与外设访问 / 驱动程序开发 - -- 易失性(volatile)的正确使用 -- 寄存器映射的 C++ 封装、位域与类型安全寄存器类设计 -- MMIO(内存映射 IO)最佳实践与编译期地址验证 -- HAL 层抽象设计、GPIO/SPI/I2C/UART 等驱动封装 -- DMA 操作的 C++ 接口、定时器与 PWM、ADC/DAC 数据采集 -- 驱动程序的测试策略 - ------- - -# 第七部分:语言特性、模式与架构 - -## 第15章 静态多态、设计模式与零开销抽象 - -- 静态多态(CRTP)与零开销抽象 -- 非虚拟接口 (NVI) 与静态多态替代虚函数 -- 策略模式(policy-based design)、可插拔组件 -- 空基类优化(EBO)、trivial/standard layout 类型 -- 练习:使用 CRTP 重写驱动抽象层并比较开销 - -## 第16章 常用设计模式与模块化 - -- 状态机、观察者、策略、单例(线程安全)、工厂、命令、代理等模式的嵌入式实现 -- 面向接口编程、依赖注入在嵌入式中的应用 -- 模块化通信协议设计、组件化架构 -- C++20 Modules 初探、头文件组织与编译优化 -- ABI 稳定性考虑 - -## 第17章 实时系统编程(更深) - -- 确定性执行与时间约束、调度策略 -- 优先级反转问题与应对技术 -- 时间管理与 chrono 库、周期性任务实现与 jitter 控制 -- 练习:实时系统调度与抖动测量 - ------- - -# 第八部分:性能分析、编译器与优化(源码到汇编) - -## 第18章 性能分析与剖析(如何找到真正的瓶颈) - -- 仪器化 vs 采样分析(SWO、ITM、SEGGER RTT、gprof、perf) -- 基于周期计数的微基准(DWT->CYCCNT、PMU) -- 二进制大小分析(arm-none-eabi-size、readelf、objdump) -- 案例:定位一次高延迟问题的完整流程 -- 练习:为一个功能写 microbench 并解释结果(避免误导性基准) - -## 第19章 编译器与优化技巧(深入理解生成代码) - -- inline、force_inline、函数边界的成本 -- 寄存器使用、栈帧、尾调用优化 -- 代码对齐与分支预测友好编写(避免分支错判的微优化) -- 源码到汇编:读懂关键函数的汇编输出并优化 -- 练习:对比三种实现(循环展开、递归、手写汇编),分析生成代码 - ------- - -# 第九部分:功耗、网络与数据处理 - -## 第20章 能耗优化(面向电池供电设备) - -- 省电模式的 API 设计(睡眠/唤醒、外设休眠) -- 代码与硬件配合减少唤醒次数(批量传输、采样策略) -- 编译与链接对能耗的间接影响(代码大小、缓存命中) -- 练习:实现低功耗传感器节点并测量平均电流(理论估算) - -## 第21章 网络、序列化与边缘数据处理 - -- 轻量协议选择(MQTT-SN、CoAP、CBOR、protobuf-lite) -- 内存与零拷贝序列化技巧 -- 流式处理与管道化设计(减少内存复制) -- 练习:实现一个零拷贝的消息传输层(SPI/串口 + CBOR) - ------- - -# 第十部分:安全、可靠性、测试与 CI - -## 第22章 安全编程实践 - -- 缓冲区溢出 / 整数溢出防护 -- 安全的字符串操作与加密库集成 -- 安全启动(Secure Boot)、固件更新 OTA、代码签名与验证 - -## 第23章 测试、静态分析与持续集成 - -- 静态分析工具(clang-tidy、cppcheck)和编译警告策略 -- 单元测试(主机端测试 / Google Test)、Mock 对象与硬件模拟 -- 集成测试、硬件在环(HIL)、Fuzzing 与边界测试 -- 防止回归的 CI/CD(交叉构建 + 仿真测试) -- 练习:为关键模块编写单元测试并在 CI 里运行 - ------- - -# 第十一部分:嵌入式 Linux 专题 - -## 第24章 Linux 系统编程基础 - -- 文件 IO 与文件描述符、sysfs 与设备文件访问 -- ioctl、mmap、信号处理、进程间通信(IPC) -- 多线程编程(std::thread)与用户态/内核态成本 - -## 第25章 Linux 设备交互与系统服务 - -- GPIO 通过 sysfs/libgpiod 操作、I2C/SPI 串口编程、网络 socket -- USB、V4L2 视频采集 -- 守护进程(Daemon)编写、systemd 服务集成、D-Bus、syslog -- 权限管理与系统资源监控 - ------- - -# 第十二部分:协程、异步与高级主题 - -## 第26章 协程与异步编程 - -- C++20 协程(Coroutines)基础 -- 异步 IO 模式、生成器(Generator)实现 -- 任务调度器设计、协程在状态机中的应用 -- 协程的性能考虑、async/await 模式 -- 练习:用 coroutine 改写一段异步 IO 流程并分析栈/内存开销 - -## 第27章 功耗管理(深入) - -- 低功耗模式控制、DVFS、外设电源管理 -- 睡眠模式与唤醒机制、能耗测量方法 -- 电池管理策略 - -## 第28章 高级安全与可靠性 - -- 代码签名、固件回滚设计、OTA 升级策略 -- 故障恢复与可靠性工程 - ------- - -# 第十三部分:综合案例研究与实战项目 - -在想了在想了 - ------- - -# 附录 - -## 附录 A:常用设计模板与代码片段(可直接拷贝) - -- 固定池、ring-buffer、DMA wrapper、外设 RAII 模板、ISR-safe queue -- CMake Cross-build 模板、链接脚本示例 - -## 附录 B:调试与测量工具清单(命令与示例) - -- GDB/LLDB、OpenOCD、J-Link、SEGGER RTT、perf、valgrind(对 Linux)、QEMU 启动示例、SWO/ITM 使用 - -## 附录 C:标准库与第三方库参考 - -- 嵌入式可用的 STL 组件、ETL、各编译器支持情况、裁剪策略 - -## 附录 D:编码规范与术语表 - -- 命名约定、代码风格、注释规范、MISRA C++ 规则精选 -- 性能陷阱清单、常见错误与对策 - -## 附录 E:参考阅读与资源(书籍、博客、标准文档、工具链手册) diff --git a/drafts/Content-Table-TemplateGuide.md b/drafts/Content-Table-TemplateGuide.md deleted file mode 100644 index 7adb35852..000000000 --- a/drafts/Content-Table-TemplateGuide.md +++ /dev/null @@ -1,325 +0,0 @@ -# C++ 模板编程全面指南 - 内容表 - -> **定位:** 一套媲美《C++ Templates: The Complete Guide》的全面模板编程指南 -> -> **覆盖标准:** C++11/14 → C++17 → C++20/23 -> -> **目标受众:** 混合层次(兼顾初学者和有经验的开发者) -> -> **核心特色:** 标准库源码解析 + 通用泛型编程理论 + 编译期元编程 + 嵌入式/MCU实战 - ---- - -## 卷一:模板基础(C++11/14 核心机制) - -**定位:** 从零开始建立完整的模板基础知识体系,适合初学者和需要巩固基础的开发者。 - -### T1.0 -- 啥是模板,为什么要有模板。模板如何进行学习? - -### T1.1 函数模板 -- 模板参数推导规则 -- 尾随返回类型与返回类型推导 -- 模板重载与 specialization -- **实战:** 实现通用的 `min/max/clamp` 函数族 -- **嵌入式贴士:** 避免代码膨胀的技巧 - -### T1.2 类模板 -- 类模板声明与定义 -- 模板参数的默认值 -- 成员函数模板 -- 虚函数与模板的限制 -- **实战:** 实现一个固定容量的 `ring_buffer` -- **调试技巧:** 理解模板实例化错误信息 - -### T1.3 模板特化与偏特化 -- 全特化 vs 偏特化 -- 类模板偏特化的匹配规则 -- 函数模板的重载替代特化 -- **实战:** 为指针类型特化 `traits` 类 -- **标准库溯源:** `std::iterator_traits` 的指针特化 - -### T1.4 非类型模板参数 -- 整数、指针、引用类型的非类型参数 -- auto 作为非类型模板参数类型(C++17) -- **实战:** 编译期数组大小、位掩码生成器 -- **嵌入式应用:** 寄存器地址的编译期封装 - -### T1.5 模板参数依赖与名字查找 -- 依赖名称(Dependent Names) -- 两阶段查找(Two-Phase Lookup) -- typename 和 template 关键字的必要性 -- ADL(Argument-Dependent Lookup)详解 -- **实战:** 正确编写泛型迭代器代码 -- **常见陷阱:** 为什么 `t.clear()` 有时不工作? - -### T1.6 模板友元与 Barton-Nackman Trick -- 友元注入机制 -- Barton-Nackman 模式(CRTP 前身) -- 运算符重载的模板技巧 -- **实战:** 实现可比较的 `Point` 类型 - -### T1.7 模板别名与 Using 声明 -- typedef vs using -- 别名模板(Alias Templates) -- **标准库溯源:** `std::conditional_t`、`std::enable_if_t` -- **实战:** 简化复杂模板类型的声明 - -### T1.8 模板与继承 -- 奇异递归模板模式(CRTP)详解 -- 静态多态 vs 动态多态 -- 混入(Mixin)模式 -- **实战:** 使用 CRTP 实现单例基类、计数器基类 -- **性能分析:** CRTP vs 虚函数的汇编对比 - -### 卷一综合项目:实现一个轻量级的 `fixed_vector` -- 完整的迭代器支持(含 const_iterator) -- 与 `std::vector` 兼容的接口 -- 编译期边界检查(可选) - ---- - -## 卷二:现代模板技术(C++17 特性) - -**定位:** 掌握现代C++模板编程的核心工具,大幅提升代码表达力和编译期计算能力。 - -### T2.1 类型萃取(Type Traits)深度解析 -- `` 全景概览 -- 类型检查、转换、修改三大类别 -- 常用 traits 详解:`is_same`、`remove_reference`、`decay`、`invoke_result` -- **实战:** 实现 `is_iterable`、`is_smart_pointer` 自定义 traits -- **标准库深潜:** `std::iterator_traits` 完整实现 - -### T2.2 SFINAE 与替换失败并非错误 -- SFINAE 原理详解 -- `std::enable_if` 的多种写法 -- 函数重载决议中的 SFINAE -- **实战:** 条件成员函数的实现 -- **调试技巧:** 使用 static_assert 改善错误信息 -- **常见陷阱:** 硬错误 vs SFINAE - -### T2.3 if constexpr:编译期分支 -- if constexpr 语法与语义 -- vs 传统 SFINAE 的优劣 -- 编译期递归的简化 -- **实战:** 实现 `print` 函数支持任意类型 -- **性能分析:** 零运行时开销的验证 - -### T2.4 可变参数模板(Variadic Templates) -- 参数包(Parameter Pack)详解 -- 包展开的四种方式 -- 递归展开 vs 折叠表达式 -- **实战:** 实现 `printf` 风格的类型安全日志函数 -- **标准库深潜:** `std::tuple` 的构造原理 - -### T2.5 折叠表达式(Fold Expressions) -- 一元折叠、二元折叠 -- 四种折叠模式:`, + && ||` -- **实战:** 实现 `all_of`、`any_of`、`for_each_arg` -- **性能对比:** 折叠表达式 vs 递归模板的编译时性能 - -### T2.6 完美转发(Perfect Forwarding) -- 万能引用(Universal Reference)vs 右值引用 -- 引用折叠规则 -- `std::forward` 实现原理 -- **实战:** 实现 `make_unique`、泛型工厂函数 -- **常见陷阱:** 转发引用的 auto&&、重载决议问题 - -### T2.7 constexpr 函数与编译期计算 -- constexpr 的演进(C++11→14→17) -- constexpr 函数的限制与放宽 -- constexpr lambda(C++17) -- **实战:** 编译期 CRC32、MD5 计算 -- **嵌入式应用:** 编译期查找表生成 - -### T2.8 类模板参数推导(CTAD) -- 自动推导规则 -- 推导指南(Deduction Guides) -- **实战:** 为自定义容器添加 CTAD 支持 -- **注意事项:** 隐式转换陷阱 - -### 卷二综合项目:实现一个类型安全的 `any` 类型 -- 支持任意类型的存储与检索 -- 使用 SFINAE/constexpr 优化 -- 与 `std::any` 的性能对比 - ---- - -## 卷三:元编程精要(C++20/23 约束与元编程) - -**定位:** 掌握现代C++的约束机制和高级元编程技术,编写更安全、更易维护的泛型代码。 - -### T3.1 概念(Concepts)详解 -- concept 声明与定义 -- requires 表达式语法 -- requires 子句 -- **标准库概览:** `std::integral`、`sortable`、`range` 等核心概念 -- **实战:** 定义 `Numeric`、`Addable`、`Hashable` 概念 - -### T3.2 使用 Concepts 约束模板 -- concept 作为模板参数约束 -- 缩写函数模板(Abbreviated Function Templates) -- concept 重载与优先级 -- **vs SFINAE:** 为什么 Concepts 是更好的选择 -- **实战:** 重写算法库使用 Concepts -- **嵌入式贴士:** 更清晰的编译错误信息 - -### T3.3 Requires 表达式深度解析 -- requires 表达式的四种成分 -- 简单要求、类型要求、复合要求、嵌套要求 -- **实战:** 定义复杂的概念(如 `Range`、`Iterator`) -- **标准库溯源:** `std::ranges::range` 概念定义 - -### T3.4 模板元编程(TMP)核心技巧 -- 类型列表(Type List)操作 -- 编译期映射与查找 -- 编译期算法:排序、搜索 -- **传统 TMP vs constexpr:** 迁移指南 -- **实战:** 实现 `type_list` 和对应的算法 - -### T3.5 编译期字符串处理 -- 字符串作为非类型模板参数(C++20) -- 编译期字符串操作 -- **实战:** 实现编译期正则表达式匹配 -- **嵌入式应用:** 协议解析的编译期优化 - -### T3.6 反射元编程基础 -- `std::is_aggregate`、`std::is_layout_compatible` -- 结构化绑定与聚合类型 -- **实战:** 实现 `for_each_member` 遍历结构体成员 -- **前瞻:** C++26 静态反射简介 - -### T3.7 模板实例化控制 -- 显式实例化声明与定义 -- extern template 减少编译时间 -- 实例化点(POI)详解 -- **嵌入式关键:** 控制代码膨胀的实用技巧 -- **实战:** 为常用类型显式实例化模板 - -### T3.8 模板与异常安全 -- 强异常保证(Strong Exception Guarantee) -- noexcept 在模板中的应用 -- 条件 noexcept(C++17) -- **实战:** 实现异常安全的泛型容器 - -### 卷三综合项目:实现一个 mini-STL 算法库 -- 使用 Concepts 约束所有算法 -- 实现 `sort`、`find`、`transform` 等核心算法 -- 与 `std::algorithm` 的性能对比 - ---- - -## 卷四:泛型设计模式实战(架构级应用) - -**定位:** 将模板技术应用于实际架构设计,掌握业界验证的设计模式和架构范式。 - -### T4.1 Policy-Based Design(策略设计) -- Policy Class 的定义与设计原则 -- vs 传统策略模式的优劣 -- **经典案例:** `std::allocator` 作为 policy -- **实战:** 设计可配置的智能指针(删除策略、所有权策略) -- **嵌入式应用:** 可插拔的内存管理策略 - -### T4.2 类型擦除(Type Erasure) -- 类型擦除原理 -- Small Buffer Optimization(SBO) -- **标准库深潜:** `std::function` 完整实现剖析 -- **标准库深潜:** `std::any` 实现剖析 -- **实战:** 实现 `function` -- **性能权衡:** 虚函数 vs 类型擦除 vs 模板 - -### T4.3 模板方法模式与 NVI -- 非虚拟接口(NVI)模式 -- 编译期模板方法 -- **实战:** CRTP 实现的算法框架 -- **性能分析:** vs 虚函数调用的汇编对比 - -### T4.4 工厂模式的模板实现 -- 抽象工厂的编译期版本 -- 对象构造的泛型解决方案 -- **实战:** 实现 `generic_factory` -- **嵌入式应用:** 驱动程序的编译期注册 - -### T4.5 访问者模式的模板实现 -- 传统访问者模式的局限 -- `std::variant` + `std::visit` 的现代方案 -- **实战:** 实现编译期访问者模式 -- **嵌入式应用:** 命令模式与事件分发 - -### T4.6 单例模式的线程安全实现 -- Meyer's Singleton -- 模板单例基类(CRTP) -- **实战:** `singleton` 的线程安全实现 -- **注意:** 静态初始化顺序问题 - -### T4.7 观察者模式的模板实现 -- 编译期类型安全的信号槽 -- **实战:** 实现 `signal` 和 `slot` -- **嵌入式应用:** ISR 到任务的事件分发 - -### T4.8 混入(Mixin)与组合式设计 -- CRTP 作为 Mixin 机制 -- 参数化继承 -- **实战:** 构建可组合的组件(日志、计数、锁) -- **设计原则:** Mixin vs 组合 - -### T4.9 Tag Dispatching 与类型分派 -- Iterator Tags 详解 -- 编译期算法选择 -- **标准库溯源:** `std::advance` 的 tag dispatching -- **实战:** 实现优化的算法选择器 - -### T4.10 模板与 DSL(领域特定语言) -- 内嵌 DSL 的设计原则 -- 运算符重载在 DSL 中的应用 -- **实战:** 实现类型安全的单位系统(米/秒) -- **实战:** 实现状态机编译期 DSL - -### 卷四综合项目:实现一个完整的嵌入式事件系统 -- 编译期类型安全的事件分发 -- 支持 ISR-safe 的队列 -- Policy-Based 的内存管理策略 -- 综合运用:CRTP、Type Erasure、Policy Design - ---- - -## 附录:嵌入式模板开发指南 - -### A.1 代码膨胀控制 -- extern template 实践 -- 共享基类技术 -- 模板 vs 运行时多态的权衡 - -### A.2 编译时间优化 -- 头文件组织策略 -- 预编译头(PCH) -- 模板实例化优化 - -### A.3 嵌入式友好的模板库选择 -- ETL(Embedded Template Library)简介 -- 自研模板库的最佳实践 - ---- - -## 参考资料与延伸阅读 - -### 经典书籍 -- *C++ Templates: The Complete Guide* (2nd Edition) - David Vandevoorde, Nicolai M. Josuttis -- *Modern C++ Design* - Andrei Alexandrescu -- *Effective C++* / *Effective Modern C++* - Scott Meyers - -### 在线资源 -- cppreference.com - 模板相关章节 -- C++ Standard Drafts - N4860 (C++20), N4950 (C++23) -- Fluent C++ (fluentcpp.com) -- Modernes C++ (modernescpp.com) - -### 标准库实现参考 -- libstdc++ (GCC) -- libc++ (Clang) -- MSVC STL - ---- - -**文档版本:** v1.0 -**最后更新:** 2026-03-17 diff --git a/todo/022-core-theory-expansion.md b/todo/022-core-theory-expansion.md index f60ea5146..ee8eeb6af 100644 --- a/todo/022-core-theory-expansion.md +++ b/todo/022-core-theory-expansion.md @@ -202,7 +202,6 @@ estimated_effort: epic - code/examples/theory/(每章对应的示例代码子目录) ## 参考资料 -- drafts/Content-Table-Draft.md(完整的章节规划草稿) - ISO C++23 标准文档 - ARM Cortex-M Programming Guide - MISRA C++ Guidelines diff --git a/todo/030-template-series.md b/todo/030-template-series.md index 6263ea663..3df1947c1 100644 --- a/todo/030-template-series.md +++ b/todo/030-template-series.md @@ -7,96 +7,51 @@ status: pending created: 2026-04-15 assignee: charliechen depends_on: ["architecture/002", "203"] -# NOTE: 本 TODO 已对齐到卷四 ch05 (vol4-advanced/ch05-template-metaprogramming/) -# 详细大纲见 todo/content/203-vol4-advanced-outline.md -blocks: [] +# NOTE: 本 TODO 为模板编程四卷系列的父级索引,详细清单见子 TODO 038-041 +blocks: ["038", "039", "040", "041"] estimated_effort: epic --- -# 模板编程四卷系列 +# 模板编程四卷系列(父级索引) ## 目标 -基于 drafts/Content-Table-TemplateGuide.md 规划,编写完整的模板编程四卷系列教程。从模板基础到高级元编程技术,再到嵌入式场景的实战应用,系统性地覆盖 C++ 模板的方方面面。每卷独立成章,循序渐进。 +编写完整的模板编程四卷系列教程,从模板基础到高级元编程技术,再到嵌入式场景的实战应用,系统性地覆盖 C++ 模板的方方面面。每卷独立成章,循序渐进。 -## 验收标准 -- [ ] Vol1 模板基础完成:函数模板、类模板、非类型参数、特化与偏特化 -- [ ] Vol2 现代模板完成:变参模板、折叠表达式、if constexpr、SFINAE、Concepts -- [ ] Vol3 模板元编程完成:类型萃取、编译期计算、CRTP、Policy-based design -- [ ] Vol4 模板实战完成:嵌入式场景应用、零开销抽象、代码生成技巧 -- [ ] 每卷至少 8 个章节,每章配备完整代码示例 -- [ ] 嵌入式场景示例覆盖 GPIO/UART/SPI/中断等常见场景 -- [ ] 编译期输出验证(static_assert)贯穿全文 -- [ ] 与 drafts/Content-Table-TemplateGuide.md 大纲对齐 - -## 实施说明 -模板编程是 C++ 最强大也最复杂的功能之一。本系列采取由浅入深、理论结合实践的教学策略。 - -**Vol1:模板基础** +本文件为父级索引,详细验收标准和实施说明已拆分至子 TODO: -1. 函数模板 — 定义、实例化、类型推导、显式指定模板参数、重载决议。 -2. 类模板 — 定义、成员函数、友元、静态成员、模板参数。 -3. 非类型模板参数 — 整数参数、指针参数、C++20 的浮点数和类类型 NTTP、编译期常量。 -4. 模板特化 — 全特化、偏特化的语法和应用场景(如 `vector` 的特化思想)。 -5. 模板实例化模型 — 隐式实例化、显式实例化、包含模型、分离编译模型。 -6. 名称查找与依赖类型 — 两阶段名称查找、`typename` 和 `template` 消歧义符。 -7. 模板与继承 — 模板基类名称查找、混入(Mixin)模式。 -8. 综合练习 — 实现一个泛型的环形缓冲区模板。 +| 子 TODO | 卷 | 标题 | 章节数 | 综合项目 | +|---------|-----|------|--------|----------| +| 038 | 卷一 | 模板基础(C++11/14 核心机制) | T1.0-T1.8 (9节) | `fixed_vector` | +| 039 | 卷二 | 现代模板技术(C++17 核心特性) | T2.1-T2.8 (8节) | 类型安全 `any` | +| 040 | 卷三 | 元编程精要(C++20/23 约束与元编程) | T3.1-T3.8 (8节) | mini-STL 算法库 | +| 041 | 卷四 | 泛型设计模式实战(架构级应用) | T4.1-T4.10 (10节) | 嵌入式事件系统 | -**Vol2:现代模板(C++11/14/17/20)** - -1. 变参模板(Variadic Templates) — 参数包、包展开、递归终止、sizeof...运算符。 -2. 折叠表达式(Fold Expressions) — 左折叠/右折叠、一元/二元折叠、常见应用模式。 -3. if constexpr — 编译期条件分支、替代 SFINAE 的简洁方案、与模板递归配合。 -4. SFINAE 与 enable_if — 替换失败不是错误的原则、std::enable_if、void_t 技巧、检测成员惯用法。 -5. Concepts(C++20) — concept 定义、requires 子句、requires 表达式、标准库 concepts、约束的偏序。 -6. auto 与模板 — auto 作为模板参数(C++17)、返回类型推导、decltype(auto)。 -7. 类模板参数推导(CTAD) — 推导指引、标准库 CTAD 示例。 -8. 综合练习 — 用 Concepts 重构 SFINAE 代码。 - -**Vol3:模板元编程** +## 全局验收标准 +- [ ] 四卷全部完成,每卷至少 8 个章节 +- [ ] 每章配备完整代码示例与 static_assert 验证 +- [ ] 嵌入式场景示例覆盖 GPIO/UART/SPI/中断等常见场景 +- [ ] 每卷含综合项目,项目代码可编译运行 +- [ ] 附录:代码膨胀控制、编译时间优化、嵌入式友好模板库选择 -1. 类型萃取(Type Traits) — 标准库 type_traits 分类、自定义 type trait、实现原理。 -2. 编译期计算 — 模板递归计算、constexpr 函数替代、编译期字符串处理。 -3. CRTP(Curiously Recurring Template Pattern) — 静态多态、代码复用、接口注入。 -4. Policy-Based Design — 策略类设计、组合优于继承、编译期策略选择。 -5. 表达式模板 — 延迟求值、避免临时对象(以嵌入式数学运算为例)。 -6. 标签分发(Tag Dispatch) — iterator_category 风格的编译期分派。 -7. 编译期数据结构 — 编译期链表(typelist)、编译期 map、编译期字符串。 -8. 综合练习 — 实现一个编译期消息路由框架。 +## 附录规划(四卷共用) -**Vol4:模板实战(嵌入式场景)** +### A.1 代码膨胀控制 +- extern template 实践 +- 共享基类技术 +- 模板 vs 运行时多态的权衡 -1. 硬件寄存器抽象 — 使用模板实现类型安全的寄存器访问、位域操作、零开销抽象。 -2. 引脚配置模板 — 编译期 GPIO 配置、端口/引号参数化、配置验证。 -3. 中断处理模板 — 编译期中断向量表生成、类型安全的 ISR 注册。 -4. DMA 描述符模板 — 编译期 DMA 传输描述、类型安全的缓冲区管理。 -5. 通信协议模板 — 串行通信协议的模板化封装(SPI/I2C/UART)。 -6. 状态机模板 — 基于模板的编译期状态机、表格驱动状态机。 -7. 零开销抽象实战 — 性能对比:模板方案 vs C 方案 vs 虚函数方案。 -8. 代码膨胀控制 — 模板实例化管理、显式实例化、避免不必要膨胀的策略。 +### A.2 编译时间优化 +- 头文件组织策略 +- 预编译头(PCH) +- 模板实例化优化 -## 涉及文件 -- documents/general/templates/vol1-basics/index.md -- documents/general/templates/vol1-basics/01-function-templates.md -- documents/general/templates/vol1-basics/02-class-templates.md -- documents/general/templates/vol1-basics/03-nttp.md -- documents/general/templates/vol1-basics/04-specialization.md -- documents/general/templates/vol1-basics/05-instantiation.md -- documents/general/templates/vol1-basics/06-name-lookup.md -- documents/general/templates/vol1-basics/07-inheritance.md -- documents/general/templates/vol1-basics/08-exercise-ring-buffer.md -- documents/general/templates/vol2-modern/index.md -- documents/general/templates/vol2-modern/ (01-08 章节) -- documents/general/templates/vol3-metaprogramming/index.md -- documents/general/templates/vol3-metaprogramming/ (01-08 章节) -- documents/general/templates/vol4-practice/index.md -- documents/general/templates/vol4-practice/ (01-08 章节) -- drafts/Content-Table-TemplateGuide.md (参考大纲) +### A.3 嵌入式友好的模板库选择 +- ETL(Embedded Template Library)简介 +- 自研模板库的最佳实践 ## 参考资料 -- drafts/Content-Table-TemplateGuide.md — 项目内规划大纲 - 《C++ Templates: The Complete Guide》(2nd Edition) — David Vandevoorde et al. - 《Modern C++ Design》— Andrei Alexandrescu - 《C++ Template Metaprogramming》— Abrahams & Gurtovoy - cppreference.com Templates 章节 --Meeting C++ 和 CppCon 模板相关演讲 +- Meeting C++ 和 CppCon 模板相关演讲 diff --git a/todo/038-template-vol1-basics.md b/todo/038-template-vol1-basics.md new file mode 100644 index 000000000..8e1df492f --- /dev/null +++ b/todo/038-template-vol1-basics.md @@ -0,0 +1,118 @@ +--- +id: 038 +title: "模板编程卷一:模板基础(C++11/14 核心机制)" +category: content +priority: P2 +status: pending +created: 2026-05-22 +assignee: charliechen +depends_on: + - "030" +blocks: [] +estimated_effort: epic +--- + +# 模板编程卷一:模板基础(C++11/14 核心机制) + +## 目标 +从零开始建立完整的模板基础知识体系。覆盖函数模板、类模板、特化与偏特化、非类型参数、名称查找、友元注入、别名、继承与 CRTP,最终通过综合项目 `fixed_vector` 巩固全部知识点。 + +## 验收标准 +- [ ] T1.0 模板导论完成:为什么需要模板、模板的学习路径 +- [ ] T1.1 函数模板完成 +- [ ] T1.2 类模板完成 +- [ ] T1.3 模板特化与偏特化完成 +- [ ] T1.4 非类型模板参数完成 +- [ ] T1.5 模板参数依赖与名字查找完成 +- [ ] T1.6 模板友元与 Barton-Nackman Trick 完成 +- [ ] T1.7 模板别名与 Using 声明完成 +- [ ] T1.8 模板与继承(CRTP)完成 +- [ ] 卷一综合项目:`fixed_vector` 完成 +- [ ] 每节包含完整代码示例与 static_assert 验证 +- [ ] 嵌入式贴士至少出现 3 处(代码膨胀、编译错误调试、寄存器封装) + +## 实施说明 + +### T1.0 模板导论 +- 什么是模板,为什么需要模板(泛型编程 vs 宏 vs 手写多版本) +- 模板的学习策略:编译器思维、错误信息解读、渐进式深入 +- 本章知识地图与后续卷的关系 + +### T1.1 函数模板 +- [ ] 模板参数推导规则(值类别、数组到指针衰减、函数到函数指针) +- [ ] 尾随返回类型(`auto foo(T t) -> decltype(...)`)与返回类型推导 +- [ ] 模板重载与 specialization 的区别 +- [ ] **实战:** 实现通用 `min/max/clamp` 函数族 +- [ ] **嵌入式贴士:** 避免代码膨胀的技巧(共享非模板基类、显式实例化) + +### T1.2 类模板 +- [ ] 类模板声明与定义(类内 vs 类外定义成员函数) +- [ ] 模板参数的默认值 +- [ ] 成员函数模板(与类模板参数独立) +- [ ] 虚函数与模板的限制(为何虚函数不能是模板) +- [ ] **实战:** 实现固定容量的 `ring_buffer` +- [ ] **调试技巧:** 理解模板实例化错误信息(常见模式与快速定位方法) + +### T1.3 模板特化与偏特化 +- [ ] 全特化 vs 偏特化的语法与语义差异 +- [ ] 类模板偏特化的匹配规则(部分匹配、递归偏特化) +- [ ] 函数模板的重载替代特化(为什么函数模板不推荐偏特化) +- [ ] **实战:** 为指针类型特化 `traits` 类 +- [ ] **标准库溯源:** `std::iterator_traits` 的指针特化实现 + +### T1.4 非类型模板参数(NTTP) +- [ ] 整数、指针、引用类型的非类型参数 +- [ ] `auto` 作为非类型模板参数类型(C++17) +- [ ] **实战:** 编译期数组大小参数化、位掩码生成器 +- [ ] **嵌入式应用:** 寄存器地址的编译期封装(`Register<0x40010800>`) + +### T1.5 模板参数依赖与名字查找 +- [ ] 依赖名称(Dependent Names)与非依赖名称 +- [ ] 两阶段查找(Two-Phase Lookup)的原理与实际行为 +- [ ] `typename` 和 `template` 关键字消歧义的必要性 +- [ ] ADL(Argument-Dependent Lookup)详解及其与模板的交互 +- [ ] **实战:** 正确编写泛型迭代器代码(避免名字查找陷阱) +- [ ] **常见陷阱:** 为什么 `t.clear()` 有时不工作(基类模板中的名称查找) + +### T1.6 模板友元与 Barton-Nackman Trick +- [ ] 友元注入机制(友元定义在类模板内部) +- [ ] Barton-Nackman 模式(CRTP 前身,通过友元注入运算符) +- [ ] 运算符重载的模板技巧(对称运算符、隐式转换) +- [ ] **实战:** 实现可比较的 `Point` 类型(`==`、`<`、`<=` 等全套运算符) + +### T1.7 模板别名与 Using 声明 +- [ ] `typedef` vs `using` 的差异与偏好 +- [ ] 别名模板(Alias Templates)的能力与限制 +- [ ] **标准库溯源:** `std::conditional_t`、`std::enable_if_t` 的别名模板实现 +- [ ] **实战:** 简化复杂模板类型的声明(嵌套模板、函数指针类型) + +### T1.8 模板与继承 +- [ ] CRTP(Curiously Recurring Template Pattern)详解 +- [ ] 静态多态 vs 动态多态的性能对比与适用场景 +- [ ] 混入(Mixin)模式:通过模板继承注入功能 +- [ ] **实战:** 使用 CRTP 实现单例基类、计数器基类 +- [ ] **性能分析:** CRTP vs 虚函数的汇编对比(Godbolt 验证零开销) + +### 卷一综合项目:`fixed_vector` +- [ ] 完整的迭代器支持(含 `const_iterator`、`reverse_iterator`) +- [ ] 与 `std::vector` 兼容的接口(`push_back`、`pop_back`、`size`、`capacity`) +- [ ] 编译期边界检查(可选,通过模板参数控制) +- [ ] 综合运用:类模板、非类型参数、特化、CRTP、友元 + +## 涉及文件 +- documents/vol4-advanced/ch05-template-metaprogramming/vol1-basics/index.md +- documents/vol4-advanced/ch05-template-metaprogramming/vol1-basics/01-introduction.md +- documents/vol4-advanced/ch05-template-metaprogramming/vol1-basics/02-function-templates.md +- documents/vol4-advanced/ch05-template-metaprogramming/vol1-basics/03-class-templates.md +- documents/vol4-advanced/ch05-template-metaprogramming/vol1-basics/04-specialization.md +- documents/vol4-advanced/ch05-template-metaprogramming/vol1-basics/05-nttp.md +- documents/vol4-advanced/ch05-template-metaprogramming/vol1-basics/06-name-lookup.md +- documents/vol4-advanced/ch05-template-metaprogramming/vol1-basics/07-friends-barton-nackman.md +- documents/vol4-advanced/ch05-template-metaprogramming/vol1-basics/08-aliases.md +- documents/vol4-advanced/ch05-template-metaprogramming/vol1-basics/09-inheritance-crtp.md +- documents/vol4-advanced/ch05-template-metaprogramming/vol1-basics/10-project-fixed-vector.md + +## 参考资料 +- 《C++ Templates: The Complete Guide》(2nd Edition) Part I & II +- cppreference.com Templates 章节 +- Godbolt (godbolt.org) 编译器浏览器 diff --git a/todo/039-template-vol2-modern.md b/todo/039-template-vol2-modern.md new file mode 100644 index 000000000..8f398a9c0 --- /dev/null +++ b/todo/039-template-vol2-modern.md @@ -0,0 +1,121 @@ +--- +id: 039 +title: "模板编程卷二:现代模板技术(C++17 核心特性)" +category: content +priority: P2 +status: pending +created: 2026-05-22 +assignee: charliechen +depends_on: + - "038" +blocks: [] +estimated_effort: epic +--- + +# 模板编程卷二:现代模板技术(C++17 核心特性) + +## 目标 +掌握现代 C++ 模板编程的核心工具,大幅提升代码表达力和编译期计算能力。覆盖 type traits、SFINAE、if constexpr、变参模板、折叠表达式、完美转发、constexpr、CTAD,最终通过综合项目实现类型安全的 `any` 类型。 + +## 验收标准 +- [ ] T2.1 类型萃取深度解析完成 +- [ ] T2.2 SFINAE 与 enable_if 完成 +- [ ] T2.3 if constexpr 完成 +- [ ] T2.4 可变参数模板完成 +- [ ] T2.5 折叠表达式完成 +- [ ] T2.6 完美转发完成 +- [ ] T2.7 constexpr 与编译期计算完成 +- [ ] T2.8 CTAD 完成 +- [ ] 卷二综合项目:类型安全的 `any` 完成 +- [ ] 每节包含 SFINAE/constexpr 版本的对比示例 +- [ ] 标准库溯源至少覆盖 3 个(iterator_traits、tuple、enable_if_t) + +## 实施说明 + +### T2.1 类型萃取(Type Traits)深度解析 +- [ ] `` 全景概览(三类:检查、转换、修改) +- [ ] 类型检查 traits:`is_integral`、`is_pointer`、`is_class`、`is_same` +- [ ] 类型转换 traits:`remove_reference`、`remove_const`、`decay`、`common_type` +- [ ] 类型修改 traits:`conditional`、`enable_if`、`void_t` +- [ ] `invoke_result` 与 callable traits +- [ ] **实战:** 实现 `is_iterable`(检测是否有 `begin()`/`end()`)、`is_smart_pointer`(检测 `unique_ptr`/`shared_ptr`) +- [ ] **标准库深潜:** `std::iterator_traits` 完整实现剖析(含指针偏特化) + +### T2.2 SFINAE 与替换失败并非错误 +- [ ] SFINAE 原理详解:模板参数替换 vs 实例化的区别 +- [ ] `std::enable_if` 的三种写法(模板参数、函数参数、返回类型) +- [ ] 函数重载决议中的 SFINAE 交互 +- [ ] `void_t` 技巧与检测成员惯用法(detect idiom) +- [ ] **实战:** 条件成员函数的实现(仅当 `T` 可序列化时提供 `serialize()`) +- [ ] **调试技巧:** 使用 `static_assert` 改善 SFINAE 的错误信息 +- [ ] **常见陷阱:** 硬错误(hard error)vs SFINAE 友好的失败;为什么有些替换失败不触发 SFINAE + +### T2.3 if constexpr:编译期分支 +- [ ] `if constexpr` 语法与语义(C++17) +- [ ] `if constexpr` vs 传统 SFINAE 的优劣对比 +- [ ] 编译期递归的简化(替代模板特化递归终止) +- [ ] `if constexpr` 的限制(不能脱离 `if` 上下文、extern 模板交互) +- [ ] **实战:** 实现 `print` 函数支持任意类型(容器、tuple、基本类型的统一输出) +- [ ] **性能分析:** 零运行时开销的验证(Godbolt 汇编对比) + +### T2.4 可变参数模板(Variadic Templates) +- [ ] 参数包(Parameter Pack)详解:`typename... Types`、`Types... args` +- [ ] 包展开(Pack Expansion)的四种方式:函数参数、模板参数、基类列表、初始化列表 +- [ ] `sizeof...` 运算符 +- [ ] 递归展开 vs 折叠表达式的演进关系 +- [ ] **实战:** 实现 `printf` 风格的类型安全日志函数(编译期格式检查) +- [ ] **标准库深潜:** `std::tuple` 的构造原理(递归继承实现) + +### T2.5 折叠表达式(Fold Expressions) +- [ ] 一元折叠:`(... op pack)` vs `(pack op ...)` +- [ ] 二元折叠:`(init op ... op pack)` vs `(pack op ... op init)` +- [ ] 四种折叠模式的实际应用:`,`(顺序执行)、`+`(求和)、`&&`(全部满足)、`||`(任一满足) +- [ ] 空包展开的规则与默认值 +- [ ] **实战:** 实现 `all_of`、`any_of`、`for_each_arg`(折叠表达式版本) +- [ ] **性能对比:** 折叠表达式 vs 递归模板的编译时性能与代码生成 + +### T2.6 完美转发(Perfect Forwarding) +- [ ] 万能引用(Universal Reference / Forwarding Reference)vs 右值引用的区分 +- [ ] 引用折叠规则(四种组合推导) +- [ ] `std::forward` 实现原理与正确使用方式 +- [ ] `std::move` vs `std::forward` 的选择 +- [ ] **实战:** 实现 `make_unique`、泛型工厂函数 `make(args...)` +- [ ] **常见陷阱:** 转发引用的 `auto&&` 用法、重载决议中的完美转发拦截问题、按值传递 vs 完美转发的权衡 + +### T2.7 constexpr 函数与编译期计算 +- [ ] `constexpr` 的演进:C++11(单 return)→ C++14(循环、局部变量)→ C++17(if、更多放宽) +- [ ] constexpr 函数的限制与放宽时间线 +- [ ] `constexpr` lambda(C++17) +- [ ] `constexpr` if 与 `constexpr` 函数的配合 +- [ ] **实战:** 编译期 CRC32 计算、编译期 MD5 计算 +- [ ] **嵌入式应用:** 编译期查找表生成(LUT)替代运行时计算 + +### T2.8 类模板参数推导(CTAD) +- [ ] 自动推导规则(从构造函数参数推导) +- [ ] 推导指南(Deduction Guides)的语法与自定义 +- [ ] 标准库 CTAD 示例:`std::pair`、`std::tuple`、`std::vector` +- [ ] **实战:** 为自定义容器 `ring_buffer` 添加 CTAD 支持 +- [ ] **注意事项:** 隐式转换陷阱、CTAD 与 `explicit` 构造函数的交互 + +### 卷二综合项目:类型安全的 `any` 类型 +- [ ] 支持任意类型的存储与检索(`any::any()`、`any_cast`) +- [ ] 使用 SFINAE / `if constexpr` 优化类型检查 +- [ ] Small Buffer Optimization(SBO)策略 +- [ ] 与 `std::any` 的接口兼容与性能对比 + +## 涉及文件 +- documents/vol4-advanced/ch05-template-metaprogramming/vol2-modern/index.md +- documents/vol4-advanced/ch05-template-metaprogramming/vol2-modern/01-type-traits.md +- documents/vol4-advanced/ch05-template-metaprogramming/vol2-modern/02-sfinae.md +- documents/vol4-advanced/ch05-template-metaprogramming/vol2-modern/03-if-constexpr.md +- documents/vol4-advanced/ch05-template-metaprogramming/vol2-modern/04-variadic-templates.md +- documents/vol4-advanced/ch05-template-metaprogramming/vol2-modern/05-fold-expressions.md +- documents/vol4-advanced/ch05-template-metaprogramming/vol2-modern/06-perfect-forwarding.md +- documents/vol4-advanced/ch05-template-metaprogramming/vol2-modern/07-constexpr.md +- documents/vol4-advanced/ch05-template-metaprogramming/vol2-modern/08-ctad.md +- documents/vol4-advanced/ch05-template-metaprogramming/vol2-modern/09-project-any.md + +## 参考资料 +- 《C++ Templates: The Complete Guide》(2nd Edition) Part III & IV +- 《Effective Modern C++》— Scott Meyers(Item 1-8, 26-30) +- cppreference.com Type Traits / Fold Expressions 章节 diff --git a/todo/040-template-vol3-metaprogramming.md b/todo/040-template-vol3-metaprogramming.md new file mode 100644 index 000000000..c472c297f --- /dev/null +++ b/todo/040-template-vol3-metaprogramming.md @@ -0,0 +1,114 @@ +--- +id: 040 +title: "模板编程卷三:元编程精要(C++20/23 约束与元编程)" +category: content +priority: P2 +status: pending +created: 2026-05-22 +assignee: charliechen +depends_on: + - "039" +blocks: [] +estimated_effort: epic +--- + +# 模板编程卷三:元编程精要(C++20/23 约束与元编程) + +## 目标 +掌握现代 C++ 的约束机制和高级元编程技术。覆盖 Concepts、requires 表达式、TMP 核心技巧、编译期字符串、反射基础、实例化控制、异常安全,最终通过综合项目实现 mini-STL 算法库。 + +## 验收标准 +- [ ] T3.1 Concepts 详解完成 +- [ ] T3.2 使用 Concepts 约束模板完成 +- [ ] T3.3 Requires 表达式深度解析完成 +- [ ] T3.4 TMP 核心技巧完成 +- [ ] T3.5 编译期字符串处理完成 +- [ ] T3.6 反射元编程基础完成 +- [ ] T3.7 模板实例化控制完成 +- [ ] T3.8 模板与异常安全完成 +- [ ] 卷三综合项目:mini-STL 算法库完成 +- [ ] 所有算法使用 Concepts 约束而非 SFINAE +- [ ] 嵌入式贴士至少 3 处(代码膨胀控制、编译期优化、ISR 友好模式) + +## 实施说明 + +### T3.1 概念(Concepts)详解 +- [ ] `concept` 声明与定义语法 +- [ ] `requires` 表达式语法(四种成分:简单要求、类型要求、复合要求、嵌套要求) +- [ ] `requires` 子句(约束模板、约束 auto) +- [ ] **标准库概览:** `std::integral`、`std::floating_point`、`std::invocable`、`std::ranges::range`、`std::sortable` 等核心概念 +- [ ] **实战:** 定义 `Numeric`(支持算术运算)、`Addable`(支持 `+`)、`Hashable`(支持 `std::hash`)概念 + +### T3.2 使用 Concepts 约束模板 +- [ ] concept 作为模板参数约束(`template`) +- [ ] 缩写函数模板(Abbreviated Function Templates:`void foo(std::integral auto x)`) +- [ ] concept 重载与约束偏序(更严格的约束优先匹配) +- [ ] **vs SFINAE:** 为什么 Concepts 是更好的选择(可读性、错误信息、编译速度) +- [ ] **实战:** 重写 `std::sort` / `std::find` 算法使用 Concepts 约束 +- [ ] **嵌入式贴士:** 更清晰的编译错误信息(减少模板错误信息迷宫) + +### T3.3 Requires 表达式深度解析 +- [ ] 简单要求(Simple Requirement):表达式合法性检查 +- [ ] 类型要求(Type Requirement):`typename` 合法性检查 +- [ ] 复合要求(Compound Requirement):`{expr} -> concept` 语法与 `noexcept` 检查 +- [ ] 嵌套要求(Nested Requirement):`requires concept_name` 子句 +- [ ] **实战:** 定义复杂概念(如 `RandomAccessIterator`、`SortableContainer`) +- [ ] **标准库溯源:** `std::ranges::range` 和 `std::ranges::random_access_range` 概念定义剖析 + +### T3.4 模板元编程(TMP)核心技巧 +- [ ] 类型列表(Type List)操作:`push_front`、`push_back`、`at`、`remove`、`unique` +- [ ] 编译期映射与查找(`type_map`、`type_set`) +- [ ] 编译期算法:排序(按大小/对齐)、搜索(按条件) +- [ ] **传统 TMP vs constexpr:** 迁移指南(何时用模板递归、何时用 constexpr 函数) +- [ ] **实战:** 实现 `type_list` 和对应的编译期算法(`for_each`、`transform`、`filter`) + +### T3.5 编译期字符串处理 +- [ ] 字符串作为非类型模板参数(C++20 `fixed_string` 惯用法) +- [ ] 编译期字符串操作:拼接、截取、比较、查找 +- [ ] **实战:** 实现编译期正则表达式匹配(简化版,支持 `*` 和 `?` 通配符) +- [ ] **嵌入式应用:** 协议解析的编译期优化(AT 命令匹配、寄存器名称映射) + +### T3.6 反射元编程基础 +- [ ] `std::is_aggregate`、`std::is_layout_compatible` 的使用 +- [ ] 结构化绑定与聚合类型的关系 +- [ ] **实战:** 实现 `for_each_member` 遍历结构体成员(基于结构化绑定 + 聚合检测) +- [ ] **前瞻:** C++26 静态反射(Reflection)提案简介(`^T`、`std::meta::info`) + +### T3.7 模板实例化控制 +- [ ] 显式实例化声明(`extern template`)与定义(`template class`) +- [ ] `extern template` 减少编译时间的原理与最佳实践 +- [ ] 实例化点(Point of Instantiation, POI)详解 +- [ ] **嵌入式关键:** 控制代码膨胀的实用技巧(共享非模板基类、显式实例化常用类型、Pimpl + 模板) +- [ ] **实战:** 为 `ring_buffer` 等常用类型显式实例化,对比编译产物大小 + +### T3.8 模板与异常安全 +- [ ] 强异常保证(Strong Exception Guarantee)在模板中的重要性 +- [ ] `noexcept` 在模板中的应用(`noexcept(noexcept(expr))` 模式) +- [ ] 条件 `noexcept`(C++17):基于类型特征的 noexcept 规范 +- [ ] `std::is_nothrow_constructible`、`std::is_nothrow_assignable` 等类型检查 +- [ ] **实战:** 实现异常安全的泛型容器(`swap` 保证不抛出、移动优于拷贝的策略) + +### 卷三综合项目:mini-STL 算法库 +- [ ] 使用 Concepts 约束所有算法参数 +- [ ] 实现 `sort`(快速排序 + 插入排序混合)、`find`(线性搜索)、`transform`(元素映射) +- [ ] 实现 `accumulate`、`count_if`、`remove_if`(稳定删除) +- [ ] 与 `std::algorithm` 的性能对比(编译时间 + 运行时间) +- [ ] 嵌入式裁剪版本:无异常、无 RTTI、固定大小容器优先 + +## 涉及文件 +- documents/vol4-advanced/ch05-template-metaprogramming/vol3-metaprogramming/index.md +- documents/vol4-advanced/ch05-template-metaprogramming/vol3-metaprogramming/01-concepts.md +- documents/vol4-advanced/ch05-template-metaprogramming/vol3-metaprogramming/02-constraining-templates.md +- documents/vol4-advanced/ch05-template-metaprogramming/vol3-metaprogramming/03-requires-expressions.md +- documents/vol4-advanced/ch05-template-metaprogramming/vol3-metaprogramming/04-tmp-techniques.md +- documents/vol4-advanced/ch05-template-metaprogramming/vol3-metaprogramming/05-compile-time-strings.md +- documents/vol4-advanced/ch05-template-metaprogramming/vol3-metaprogramming/06-reflection-basics.md +- documents/vol4-advanced/ch05-template-metaprogramming/vol3-metaprogramming/07-instantiation-control.md +- documents/vol4-advanced/ch05-template-metaprogramming/vol3-metaprogramming/08-exception-safety.md +- documents/vol4-advanced/ch05-template-metaprogramming/vol3-metaprogramming/09-project-mini-stl.md + +## 参考资料 +- 《C++ Templates: The Complete Guide》(2nd Edition) Part V +- 《C++ Concurrency in Action》(2nd Edition) — 异常安全章节 +- cppreference.com Concepts / Ranges 章节 +- C++26 反射提案(P2996) diff --git a/todo/041-template-vol4-design-patterns.md b/todo/041-template-vol4-design-patterns.md new file mode 100644 index 000000000..b7d46d1c8 --- /dev/null +++ b/todo/041-template-vol4-design-patterns.md @@ -0,0 +1,128 @@ +--- +id: 041 +title: "模板编程卷四:泛型设计模式实战(架构级应用)" +category: content +priority: P2 +status: pending +created: 2026-05-22 +assignee: charliechen +depends_on: + - "040" +blocks: [] +estimated_effort: epic +--- + +# 模板编程卷四:泛型设计模式实战(架构级应用) + +## 目标 +将模板技术应用于实际架构设计,掌握业界验证的设计模式和架构范式。覆盖 Policy-Based Design、Type Erasure、NVI、工厂/访问者/单例/观察者模式的模板实现、Mixin 组合、Tag Dispatching、DSL 构建,最终通过综合项目实现完整的嵌入式事件系统。 + +## 验收标准 +- [ ] T4.1 Policy-Based Design 完成 +- [ ] T4.2 类型擦除(Type Erasure)完成 +- [ ] T4.3 模板方法模式与 NVI 完成 +- [ ] T4.4 工厂模式模板实现完成 +- [ ] T4.5 访问者模式模板实现完成 +- [ ] T4.6 单例模式线程安全实现完成 +- [ ] T4.7 观察者模式模板实现完成 +- [ ] T4.8 Mixin 与组合式设计完成 +- [ ] T4.9 Tag Dispatching 与类型分派完成 +- [ ] T4.10 模板与 DSL 完成 +- [ ] 卷四综合项目:嵌入式事件系统完成 +- [ ] 每个模式提供虚函数 vs 模板的性能对比 +- [ ] 嵌入式应用案例至少 5 处 + +## 实施说明 + +### T4.1 Policy-Based Design(策略设计) +- [ ] Policy Class 的定义与设计原则(正交性、可组合性) +- [ ] vs 传统策略模式(运行时多态)的优劣 +- [ ] **经典案例:** `std::allocator` 作为 policy 的设计分析 +- [ ] **实战:** 设计可配置的智能指针(删除策略:数组 vs 单对象、所有权策略:独占 vs 共享) +- [ ] **嵌入式应用:** 可插拔的内存管理策略(静态池 / arena / 栈分配器作为策略注入) + +### T4.2 类型擦除(Type Erasure) +- [ ] 类型擦除原理:隐藏具体类型,保留统一接口 +- [ ] Small Buffer Optimization(SBO)策略与实现 +- [ ] **标准库深潜:** `std::function` 完整实现剖析(SBO + 虚函数表 + 类型擦除) +- [ ] **标准库深潜:** `std::any` 实现剖析(RTTI 的使用与 `any_cast` 的安全性) +- [ ] **实战:** 实现 `function`(支持函数指针、lambda、仿函数、成员函数) +- [ ] **性能权衡:** 虚函数 vs 类型擦除 vs 纯模板的性能对比(调用开销 + 二进制大小) + +### T4.3 模板方法模式与 NVI +- [ ] 非虚拟接口(NVI)模式:public 非虚函数调用 private 虚函数 +- [ ] 编译期模板方法:CRTP 替代虚函数的"模板方法" +- [ ] **实战:** CRTP 实现的算法框架(基类定义骨架,派生类提供策略钩子) +- [ ] **性能分析:** vs 虚函数调用的汇编对比(Godbolt 验证零开销) + +### T4.4 工厂模式的模板实现 +- [ ] 抽象工厂的编译期版本(基于 typelist 的工厂) +- [ ] 对象构造的泛型解决方案(`make_unique` / `make_shared` 的通用化) +- [ ] **实战:** 实现 `generic_factory`(支持注册创建函数、按 key 查找创建) +- [ ] **嵌入式应用:** 驱动程序的编译期注册(基于模板 + constexpr 注册表) + +### T4.5 访问者模式的模板实现 +- [ ] 传统访问者模式的局限(双重分发的复杂性、扩展困难) +- [ ] `std::variant` + `std::visit` 的现代方案(编译期 visitor) +- [ ] 泛型 lambda 作为 visitor(`overloaded` 工具实现) +- [ ] **实战:** 实现编译期访问者模式(基于 variant 的表达式树求值器) +- [ ] **嵌入式应用:** 命令模式与事件分发(variant-based 的命令处理框架) + +### T4.6 单例模式的线程安全实现 +- [ ] Meyer's Singleton(C++11 保证线程安全的局部静态变量) +- [ ] 模板单例基类(CRTP 实现:`class T : public singleton`) +- [ ] **实战:** `singleton` 的线程安全实现(含禁用拷贝/移动、显式 delete) +- [ ] **注意:** 静态初始化顺序问题(Static Initialization Order Fiasco)与解决策略 +- [ ] **嵌入式注意:** 全局构造函数的开销与 `constinit` 的替代 + +### T4.7 观察者模式的模板实现 +- [ ] 编译期类型安全的信号槽(无 Qt 依赖) +- [ ] **实战:** 实现 `signal` 和 `slot`(支持连接、断开、emit) +- [ ] **嵌入式应用:** ISR 到任务的事件分发(信号槽作为中断安全的观察者机制) +- [ ] **性能考量:** 信号槽 vs 裸回调 vs 虚函数的开销对比 + +### T4.8 混入(Mixin)与组合式设计 +- [ ] CRTP 作为 Mixin 机制(向基类注入行为) +- [ ] 参数化继承(多重继承的线性化) +- [ ] **实战:** 构建可组合的组件(日志 Mixin + 计数 Mixin + 锁 Mixin 的自由组合) +- [ ] **设计原则:** Mixin vs 组合的选择指南(何时用继承混入、何时用成员组合) + +### T4.9 Tag Dispatching 与类型分派 +- [ ] Iterator Tags 详解(`input_iterator_tag` → `forward_iterator_tag` → ... → `random_access_iterator_tag`) +- [ ] 编译期算法选择(基于 tag 选择最优实现) +- [ ] **标准库溯源:** `std::advance` 的 tag dispatching 实现剖析 +- [ ] **实战:** 实现优化的算法选择器(串行 vs 并行、内存映射 vs IO 端口) +- [ ] Tag Dispatching vs Concepts vs `if constexpr` 的选择指南 + +### T4.10 模板与 DSL(领域特定语言) +- [ ] 内嵌 DSL 的设计原则(流畅接口、运算符重载、表达式模板) +- [ ] 运算符重载在 DSL 中的应用(`+`、`|`、`>>` 等运算符的语义重载) +- [ ] **实战:** 实现类型安全的单位系统(`meter` / `second` / `meter_per_second`,编译期维度检查) +- [ ] **实战:** 实现状态机编译期 DSL(`state_machine` + `transition_table` + `guard/action`) + +### 卷四综合项目:嵌入式事件系统 +- [ ] 编译期类型安全的事件分发(基于 variant + visitor) +- [ ] 支持 ISR-safe 的队列(lock-free SPSC 环形缓冲区) +- [ ] Policy-Based 的内存管理策略(静态池 / arena 可配置) +- [ ] 综合运用:CRTP(事件处理器基类)、Type Erasure(事件存储)、Policy Design(内存策略) +- [ ] 性能验证:中断延迟测量 + 内存占用分析 + +## 涉及文件 +- documents/vol4-advanced/ch05-template-metaprogramming/vol4-design-patterns/index.md +- documents/vol4-advanced/ch05-template-metaprogramming/vol4-design-patterns/01-policy-based-design.md +- documents/vol4-advanced/ch05-template-metaprogramming/vol4-design-patterns/02-type-erasure.md +- documents/vol4-advanced/ch05-template-metaprogramming/vol4-design-patterns/03-nvi-template-method.md +- documents/vol4-advanced/ch05-template-metaprogramming/vol4-design-patterns/04-factory-pattern.md +- documents/vol4-advanced/ch05-template-metaprogramming/vol4-design-patterns/05-visitor-pattern.md +- documents/vol4-advanced/ch05-template-metaprogramming/vol4-design-patterns/06-singleton-pattern.md +- documents/vol4-advanced/ch05-template-metaprogramming/vol4-design-patterns/07-observer-pattern.md +- documents/vol4-advanced/ch05-template-metaprogramming/vol4-design-patterns/08-mixin-composition.md +- documents/vol4-advanced/ch05-template-metaprogramming/vol4-design-patterns/09-tag-dispatching.md +- documents/vol4-advanced/ch05-template-metaprogramming/vol4-design-patterns/10-dsl.md +- documents/vol4-advanced/ch05-template-metaprogramming/vol4-design-patterns/11-project-event-system.md + +## 参考资料 +- 《Modern C++ Design》— Andrei Alexandrescu(Policy-Based Design 开山之作) +- 《C++ Templates: The Complete Guide》(2nd Edition) Chapter 19-23 +- 《Game Programming Patterns》— Robert Nystrom(状态机/观察者/命令模式) +- 《Hands-On Design Patterns with C++》— Fedor G. Pikus diff --git a/todo/042-stm32f1-engineering-prep.md b/todo/042-stm32f1-engineering-prep.md new file mode 100644 index 000000000..f15824e63 --- /dev/null +++ b/todo/042-stm32f1-engineering-prep.md @@ -0,0 +1,174 @@ +--- +id: 042 +title: "STM32F1 工程准备教程(工具链 + freestanding + 启动文件 + BSP)" +category: content +priority: P1 +status: pending +created: 2026-05-22 +assignee: charliechen +depends_on: + - "architecture/002" +blocks: + - "012" + - "013" + - "014" + - "015" + - "016" + - "017" + - "018" + - "019" +estimated_effort: large +--- + +# STM32F1 工程准备教程 + +## 目标 +编写 STM32F1 系列教程的工程准备章节,作为 012-021 外设教程的前置基础。覆盖四大主题:(1) 工具链安装与交叉编译环境搭建(arm-none-eabi-gcc、CMake、VS Code 配置);(2) freestanding 策略(裸机环境下的 C++ 子集选择、禁止项与推荐项);(3) 启动文件与链接脚本(startup.s、向量表、Reset_Handler、Flash/RAM 布局);(4) 最小板级抽象(board/ 目录职责、Blue Pill 资源分配、LED/按键/串口命名规范)。 + +## 验收标准 +- [ ] 1.1 工具链与构建系统完成 +- [ ] 1.2 freestanding 策略完成 +- [ ] 1.3 启动文件与链接脚本完成 +- [ ] 1.4 最小 BSP 与板级抽象完成 +- [ ] 读者能一键编译并生成可下载固件(.elf / .bin / .hex) +- [ ] 读者能解释 `main()` 之前的启动流程 +- [ ] 读者能区分 freestanding 与 hosted 环境的差异 +- [ ] 板级资源定义集中管理,上层不直接关心 pin 号细节 +- [ ] 所有代码在 STM32F103C8T6(Blue Pill)上验证通过 + +## 实施说明 + +### 1.1 工具链与构建系统 + +**目标:** 让读者完成交叉编译环境的基本搭建,理解"为什么需要独立工具链和构建系统"。 + +- [ ] arm-none-eabi-gcc 的作用与安装 +- [ ] binutils 工具链:objdump、size、readelf 的用途 +- [ ] CMake 交叉编译配置(toolchain file 编写) +- [ ] VS Code 任务配置(build/flash 任务)和调试配置(Cortex-Debug 插件) +- [ ] 编译产物解读:`.elf`、`.bin`、`.hex` 的区别与用途 +- [ ] 烧录与下载方式(ST-Link / OpenOCD / serial bootloader) + +**C++23 关联:** +- 建议开启 `-std=c++23` +- 建议关闭异常、RTTI(`-fno-exceptions -fno-rtti`),按需讨论 +- 编译选项优化等级选择(`-Og` 调试 / `-Os` 发布) + +**验收:** +- 工程成功编译 +- 输出文件可被下载器识别 +- 板子能进入最小可运行状态 + +### 1.2 freestanding 策略 + +**目标:** 让读者明确在裸机环境下哪些 C++ 用法优先,哪些应当谨慎。 + +- [ ] freestanding 与 hosted 的区别(标准库头文件分类:freestanding vs hosted) +- [ ] 为什么裸机教程不建议一开始就依赖完整标准库生态 +- [ ] 固定容量容器优先(`std::array`、`std::span` 替代 `std::vector`) +- [ ] 运行时分配最小化(禁用/限制 `new/delete`) +- [ ] 错误处理优先返回值 / `std::expected`(替代异常) +- [ ] 输出策略:不依赖 `iostream`(自定义轻量日志) + +**输出规范建议:** +- 主路径不依赖 `new/delete` +- 主路径不依赖异常 +- 主路径不依赖 RTTI +- 主路径不依赖 iostream + +**C++23 关联:** +- `std::expected` — 统一错误处理 +- `std::span` — 安全的缓冲区传递 +- `std::array` — 固定容量容器 +- `std::byte` — 类型安全的原始字节 +- `std::to_underlying` — 枚举底层值转换 +- `constexpr` / `consteval` / `if consteval` — 编译期策略 + +**验收:** +- 读者能够明确哪些库在教程中"默认可用",哪些是"可选增强" +- 读者理解为什么这样设计更适合 STM32F103 + +### 1.3 启动文件与链接脚本 + +**目标:** 让读者知道程序为什么能从 Flash 跑起来,以及启动阶段发生了什么。 + +- [ ] 启动文件 `startup_stm32f103xb.s` 分析 +- [ ] 向量表(Vector Table)结构:栈指针 + 异常/中断入口 +- [ ] `Reset_Handler` 流程:`SystemInit` → `.data` 搬运 → `.bss` 清零 → `main` +- [ ] 数据段 `.data`:从 Flash 搬运到 RAM +- [ ] BSS 段 `.bss`:初始化为零 +- [ ] 链接脚本中的 Flash / RAM 布局(`MEMORY` 命令、`SECTIONS` 命令) +- [ ] 栈与堆的概念(栈大小配置、堆的取舍) + +**C++23 关联:** +- `constexpr` 定义内存布局常量(Flash 大小、RAM 大小、栈大小) +- 无直接语言特性为主,重点是"编译期配置"思想 + +**验收:** +- 读者能解释 `main()` 之前的流程 +- 读者知道为什么 `.data` 需要搬运、`.bss` 需要清零 +- 读者知道链接脚本为什么不是可有可无 + +### 1.4 最小 BSP 与板级抽象 + +**目标:** 建立"板级差异收口"的基本架构,让上层应用不直接关心引脚细节。 + +- [ ] `board/` 目录职责与文件组织 +- [ ] Blue Pill (STM32F103C8T6) 的板级资源分配(LED、按键、串口、调试口) +- [ ] LED / 按键 / 串口 / 调试口的命名规范 +- [ ] 板级初始化入口(`board_init()` 的职责) +- [ ] 引脚配置集中管理策略(`board_pins.hpp` 示例) + +**C++23 关联:** +- `enum class` 用于硬件资源枚举(`enum class Port : char { A, B, C }`) +- `std::to_underlying` 用于枚举索引和表驱动结构 +- `[[nodiscard]]` 标记初始化结果函数 + +**验收:** +- 板级资源定义集中在一个文件 +- 上层应用不直接写 `GPIOA, GPIO_PIN_5` 等裸常量 +- 切换板子(如 Blue Pill → 最小系统板)只需修改 board 文件 + +### 附:C++23 特性章节引入规划 + +以下是建议在 STM32F1 教程中逐步引入的 C++23 特性: + +| 特性 | 优先级 | 首次引入 | 主要用途 | +|------|--------|----------|----------| +| `std::expected` | 优先 | 1.2 | 统一错误处理 | +| `std::span` | 优先 | 1.2 | 安全缓冲区 | +| `std::array` | 优先 | 1.2 | 固定容器 | +| `std::byte` | 优先 | 1.2 | 类型安全字节 | +| `std::to_underlying` | 优先 | 1.2 | 枚举转换 | +| `constexpr` | 优先 | 1.3 | 编译期常量 | +| `consteval` | 优先 | 时钟树 | 编译期强制计算 | +| `if consteval` | 优先 | 时钟树 | 编译期分支 | +| `enum class` | 优先 | GPIO | 强类型枚举 | +| `[[nodiscard]]` | 优先 | GPIO | 忽略返回值警告 | +| `std::byteswap` | 进阶 | SPI | 字节序处理 | +| `deducing this` | 进阶 | 事件驱动 | 简化 CRTP | +| 静态 lambda | 进阶 | 驱动封装 | ISR 无捕获回调 | + +### 附:freestanding 使用约束速查 + +**默认推荐:** 固定容量容器、静态存储期对象、编译期配置、无捕获回调、显式错误返回 + +**默认谨慎:** `new/delete`、`exception`、`RTTI`、`iostream`、过度依赖动态分配的容器与算法 + +**可选扩展:** 某些 STL 算法(``)、轻量 `` 能力(`std::move`、`std::forward`)、某些工具库 + +前提是不破坏教程的"可移植、可解释、可教学"目标。 + +## 涉及文件 +- documents/vol8-domains/embedded/stm32f1/ch00-engineering-prep/index.md +- documents/vol8-domains/embedded/stm32f1/ch00-engineering-prep/01-toolchain-build.md +- documents/vol8-domains/embedded/stm32f1/ch00-engineering-prep/02-freestanding-strategy.md +- documents/vol8-domains/embedded/stm32f1/ch00-engineering-prep/03-startup-linker.md +- documents/vol8-domains/embedded/stm32f1/ch00-engineering-prep/04-board-bsp.md + +## 参考资料 +- ARM Cortex-M3 Technical Reference Manual +- STM32F103 Reference Manual (RM0008) +- GCC ARM Embedded 工具链文档 +- C++23 Freestanding 提案(P2338) +- ISO C++ Freestanding 章节([compliance]) From 232eb22352dbe7f844e765736e7584c07e982764 Mon Sep 17 00:00:00 2001 From: Charliechen114514 <725610365@qq.com> Date: Fri, 22 May 2026 12:10:30 +0800 Subject: [PATCH 2/2] feat: tidy up for vol4 templated programming --- README.md | 2 +- .../en/vol4-advanced/00-template-overview.md | 370 --- .../en/vol4-advanced/01-function-templates.md | 855 ------- .../en/vol4-advanced/02-class-templates.md | 1914 --------------- .../03-template-specialization.md | 1982 --------------- documents/en/vol4-advanced/04-if-constexpr.md | 127 - .../04-non-type-template-params.md | 1837 -------------- .../05-template-args-and-name-lookup.md | 1471 ----------- .../06-template-friends-and-barton-nackman.md | 1344 ----------- .../07-template-aliases-and-using.md | 1316 ---------- .../08-templates-and-inheritance-crtp.md | 2131 ---------------- .../en/vol4-advanced/cpp-templates-index.md | 20 - documents/en/vol4-advanced/index.md | 17 +- .../vol1-basics-cpp11-14/index.md | 23 + .../vol4-advanced/vol2-modern-cpp17/index.md | 30 + .../vol3-metaprogramming-cpp20-23/index.md | 22 + .../vol4-generics-patterns/index.md | 24 + .../embedded/core-embedded-cpp-index.md | 15 +- .../vol4-advanced/00-template-overview.md | 370 --- .../vol4-advanced/01-function-templates.md | 854 ------- documents/vol4-advanced/02-class-templates.md | 1913 --------------- .../03-template-specialization.md | 1981 --------------- documents/vol4-advanced/04-if-constexpr.md | 139 -- .../04-non-type-template-params.md | 1836 -------------- .../05-template-args-and-name-lookup.md | 1467 ----------- .../06-template-friends-and-barton-nackman.md | 1352 ----------- .../07-template-aliases-and-using.md | 1315 ---------- .../08-templates-and-inheritance-crtp.md | 2142 ----------------- .../vol4-advanced/cpp-templates-index.md | 20 - documents/vol4-advanced/index.md | 17 +- .../vol1-basics-cpp11-14/.gitkeep | 0 .../vol1-basics-cpp11-14/index.md | 23 + .../vol4-advanced/vol2-modern-cpp17/index.md | 30 + .../vol3-metaprogramming-cpp20-23/.gitkeep | 0 .../vol3-metaprogramming-cpp20-23/index.md | 22 + .../vol4-generics-patterns/.gitkeep | 0 .../vol4-generics-patterns/index.md | 24 + .../embedded/core-embedded-cpp-index.md | 15 +- todo/038-template-vol1-basics.md | 22 +- todo/039-template-vol2-modern.md | 20 +- todo/040-template-vol3-metaprogramming.md | 20 +- todo/041-template-vol4-design-patterns.md | 24 +- 42 files changed, 262 insertions(+), 26844 deletions(-) delete mode 100644 documents/en/vol4-advanced/00-template-overview.md delete mode 100644 documents/en/vol4-advanced/01-function-templates.md delete mode 100644 documents/en/vol4-advanced/02-class-templates.md delete mode 100644 documents/en/vol4-advanced/03-template-specialization.md delete mode 100644 documents/en/vol4-advanced/04-if-constexpr.md delete mode 100644 documents/en/vol4-advanced/04-non-type-template-params.md delete mode 100644 documents/en/vol4-advanced/05-template-args-and-name-lookup.md delete mode 100644 documents/en/vol4-advanced/06-template-friends-and-barton-nackman.md delete mode 100644 documents/en/vol4-advanced/07-template-aliases-and-using.md delete mode 100644 documents/en/vol4-advanced/08-templates-and-inheritance-crtp.md delete mode 100644 documents/en/vol4-advanced/cpp-templates-index.md create mode 100644 documents/en/vol4-advanced/vol1-basics-cpp11-14/index.md create mode 100644 documents/en/vol4-advanced/vol2-modern-cpp17/index.md create mode 100644 documents/en/vol4-advanced/vol3-metaprogramming-cpp20-23/index.md create mode 100644 documents/en/vol4-advanced/vol4-generics-patterns/index.md delete mode 100644 documents/vol4-advanced/00-template-overview.md delete mode 100644 documents/vol4-advanced/01-function-templates.md delete mode 100644 documents/vol4-advanced/02-class-templates.md delete mode 100644 documents/vol4-advanced/03-template-specialization.md delete mode 100644 documents/vol4-advanced/04-if-constexpr.md delete mode 100644 documents/vol4-advanced/04-non-type-template-params.md delete mode 100644 documents/vol4-advanced/05-template-args-and-name-lookup.md delete mode 100644 documents/vol4-advanced/06-template-friends-and-barton-nackman.md delete mode 100644 documents/vol4-advanced/07-template-aliases-and-using.md delete mode 100644 documents/vol4-advanced/08-templates-and-inheritance-crtp.md delete mode 100644 documents/vol4-advanced/cpp-templates-index.md delete mode 100644 documents/vol4-advanced/vol1-basics-cpp11-14/.gitkeep create mode 100644 documents/vol4-advanced/vol1-basics-cpp11-14/index.md create mode 100644 documents/vol4-advanced/vol2-modern-cpp17/index.md delete mode 100644 documents/vol4-advanced/vol3-metaprogramming-cpp20-23/.gitkeep create mode 100644 documents/vol4-advanced/vol3-metaprogramming-cpp20-23/index.md delete mode 100644 documents/vol4-advanced/vol4-generics-patterns/.gitkeep create mode 100644 documents/vol4-advanced/vol4-generics-patterns/index.md diff --git a/README.md b/README.md index cf7e34c74..2ab0de5e7 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ --- -![English Coverage](https://img.shields.io/badge/en_coverage-100%25-green.svg) 410/410 docs translated +![English Coverage](https://img.shields.io/badge/en_coverage-100%25-green.svg) 403/403 docs translated ## 这是什么项目 diff --git a/documents/en/vol4-advanced/00-template-overview.md b/documents/en/vol4-advanced/00-template-overview.md deleted file mode 100644 index 58877e526..000000000 --- a/documents/en/vol4-advanced/00-template-overview.md +++ /dev/null @@ -1,370 +0,0 @@ ---- -chapter: 12 -cpp_standard: -- 11 -- 14 -- 17 -- 20 -description: Understanding the Core Concepts and Learning Path of C++ Templates -difficulty: beginner -order: 0 -platform: host -prerequisites: -- 'Chapter 2: 零开销抽象' -- 'Chapter 11: 类型推导基础' -reading_time_minutes: 10 -tags: -- cpp-modern -- host -- intermediate -title: Template Beginner Overview ---- -# Modern C++ for Embedded Systems Tutorial — Template Basics Overview - -## Introduction: Why Do We Need Templates? - -Imagine this scenario: you are writing a communication protocol stack for an embedded project and need to handle data packets of different sizes—calculating checksums for 8-bit, 16-bit, 32-bit, and even 64-bit values. - -Using traditional C style, you might write code like this: - -```cpp -uint8_t checksum8(const uint8_t* data, size_t len) { - uint8_t sum = 0; - for (size_t i = 0; i < len; ++i) { - sum += data[i]; - } - return sum; -} - -uint16_t checksum16(const uint16_t* data, size_t len) { - uint16_t sum = 0; - for (size_t i = 0; i < len; ++i) { - sum += data[i]; - } - return sum; -} - -uint32_t checksum32(const uint32_t* data, size_t len) { - uint32_t sum = 0; - for (size_t i = 0; i < len; ++i) { - sum += data[i]; - } - return sum; -} -``` - -Three functions with identical logic, differing only in type. This is tedious to write and even more tedious to maintain—if you need to modify the checksum algorithm (say, adding overflow handling), you have to change it in three places. - -This is where C++ templates come in. - ------- - -## What Are Templates? - -**Templates are C++'s generic programming mechanism**, allowing you to write type-agnostic code and letting the compiler generate the corresponding functions or classes based on the specific types used. - -Rewriting the checksum function above using templates: - -```cpp -template -T checksum(const T* data, size_t len) { - T sum = 0; - for (size_t i = 0; i < len; ++i) { - sum += data[i]; - } - return sum; -} - -// 使用时 -uint8_t data8[16] = { /* ... */ }; -auto sum8 = checksum(data8, 16); - -uint16_t data16[8] = { /* ... */ }; -auto sum16 = checksum(data16, 8); -``` - -One piece of code, applicable to all types. The compiler automatically generates the corresponding function version based on how you call it—this process is called **template instantiation**. - -> To sum it up in one sentence: **Templates are compile-time code generators that let you write type-agnostic code while maintaining type safety.** - ------- - -## Core Value of Templates - -### 1. Type Safety + Code Reuse - -C macros can achieve a certain degree of "generic" programming, but they perform text substitution without any type checking: - -```cpp -// C风格宏 - 不安全 -#define MAX(a, b) ((a) > (b) ? (a) : (b)) - -// 问题1:多次求值 -int x = 1; -int result = MAX(++x, 10); // x被递增两次!结果可能不是你想要的 - -// 问题2:类型不匹配 -double d = MAX(3.14, "hello"); // 编译器可能不报错,但行为未定义 -``` - -Templates perform type checking at compile time, ensuring both safety and reuse: - -```cpp -template -T max(const T& a, const T& b) { - return a > b ? a : b; -} - -int x = 1; -int result = max(++x, 10); // x只递增一次,行为确定 - -// double d = max(3.14, "hello"); // 编译错误!类型不匹配 -``` - -### 2. Zero-Overhead Abstraction - -One of the core philosophies of modern C++: **abstractions should not incur runtime overhead**. - -Templates are expanded at compile time, and the generated code is indistinguishable from hand-optimized versions. Consider this example: - -```cpp -template -class FixedVector { -public: - T& operator[](std::size_t index) { - return data[index]; - } - // ... 其他成员 -private: - T data[N]; // 编译期确定大小,栈上分配 -}; - -FixedVector vec; // 编译为 int data[8],没有动态分配 -``` - -This is much better suited for embedded scenarios than `std::vector`—no heap allocation, fixed size, and a fully predictable memory layout. - -### 3. Compile-Time Computation - -Templates are the foundation of C++ metaprogramming, enabling complex calculations at compile time: - -```cpp -template -struct Factorial { - static constexpr std::size_t value = N * Factorial::value; -}; - -template<> -struct Factorial<0> { - static constexpr std::size_t value = 1; -}; - -// 编译期计算 -static_assert(Factorial<5>::value == 120); -``` - -This might look like a toy, but it is quite useful in embedded development—for example, generating lookup tables or calculating register bitmasks. - ------- - -## Templates in Embedded Development - -### Unique Advantages of Templates in Embedded Systems - -| Advantage | Description | Practical Application | -|-----------|-------------|-----------------------| -| Compile-time determination | No runtime branching | Register address mapping, protocol parsing | -| Zero heap allocation | Avoids fragmentation | Fixed-size containers, object pools | -| Type safety | Compile-time error detection | Peripheral wrappers, unit systems | -| Code inlining | Reduces function call overhead | Algorithm specialization, hot path optimization | - -### Trade-Offs to Consider - -Templates are not without their costs: - -- **Code bloat**: Each template instantiation generates a separate copy of the code, increasing Flash usage -- **Compile time**: Complex template metaprogramming can significantly increase compilation time -- **Error messages**: Template compilation errors can be extremely cryptic -- **Debugging difficulty**: The expanded template code may look very different from the source code - -Pragmatic principle: **Use templates to optimize performance on critical paths, but keep ordinary code simple and readable.** - ------- - -## Basic Types of Templates - -C++ templates are mainly divided into two categories: - -### 1. Function Templates - -Used to generate type-dependent functions: - -```cpp -template -T add(T a, T b) { - return a + b; -} - -// 或用 auto 返回类型推导 -template -auto multiply(T a, T b) -> decltype(a * b) { - return a * b; -} -``` - -### 2. Class Templates - -Used to generate type-dependent classes: - -```cpp -template -class Stack { -public: - void push(const T& item); - T pop(); - bool empty() const; -private: - std::vector data; -}; - -// 使用 -Stack int_stack; -Stack string_stack; -``` - -In addition, there are: - -- **Member templates**: Template functions inside a class -- **Variable templates**: Introduced in C++14, for variable-level templates -- **Alias templates**: Simplify complex type names - -These will be covered in detail in subsequent chapters. - ------- - -## Recommended Learning Path - -The template learning curve is steep, but following the right path can yield twice the result with half the effort: - -### Phase 1: Master the Basics (1-2 weeks) - -1. **Understand the template instantiation mechanism**: How the compiler generates concrete code from templates -2. **Function templates**: Argument deduction, return type deduction -3. **Class templates**: Basic declarations, member definitions, specialization -4. **Practical techniques**: Combining `auto`/`decltype` with templates - -### Phase 2: Dive into the Type System (2-3 weeks) - -1. **Type Traits**: Using the `` library -2. **SFINAE**: Understanding "Substitution Failure Is Not An Error" -3. **`std::enable_if`**: Techniques for conditional compilation -4. **Tag Dispatching**: Compile-time algorithm selection - -### Phase 3: Modern Template Techniques (3-4 weeks) - -1. **`constexpr`**: Compile-time computation -2. **Variadic templates**: Handling an arbitrary number of arguments -3. **Fold expressions**: Simplifying parameter pack operations -4. **`if constexpr`**: Compile-time conditional branching - -### Phase 4: C++20 Concepts (1-2 weeks) - -1. **Concept definitions**: Constraining template parameters -2. **Requires expressions**: Writing clear concepts -3. **Abbreviated function templates**: More concise syntax -4. **Concept overloading**: Smarter overload resolution - -### Learning Advice - -- **Hands-on practice**: Write code to verify every concept you learn, and check the generated assembly -- **Read the standard library**: `std::vector` and `std::algorithm` are the best textbooks -- **Gradual progression**: Don't dive into complex metaprogramming right from the start -- **Pragmatism**: In embedded development, don't force templating when a simple solution will do - ------- - -## Clarifying Common Misconceptions - -### Misconception 1: "Templates make code slower" - -**Reality**: Properly used template code has exactly the same performance as hand-written code. The compiler applies the same optimizations to template code. Optimizations like inlining, constant propagation, and dead code elimination are fully effective on template code. - -### Misconception 2: "Templates are only for library developers" - -**Reality**: Templates are a fundamental C++ feature. Understanding them helps you better use the standard library and write type-safe code. Embedded developers frequently use templates like `std::array` and `std::tuple`. - -### Misconception 3: "Template code size will always bloat" - -**Reality**: The degree of bloat depends on how you use them. It can be effectively controlled through techniques like shared base classes and `extern template` explicit instantiation. In many cases, the compile-time optimizations enabled by templates can actually reduce the final code size. - -### Misconception 4: "You must master all template tricks" - -**Reality**: Mastering the basics is enough to handle 80% of scenarios. Complex metaprogramming techniques are only needed in specific situations. - ------- - -## Hands-On: Your First Useful Template - -Let's wrap up this chapter with a practical example—a type-safe bitmask utility: - -```cpp -template -struct BitMask { - static constexpr RegType mask = static_cast(1) << Bit; - - // 设置位 - static inline RegType set(RegType reg) { - return reg | mask; - } - - // 清除位 - static inline RegType clear(RegType reg) { - return reg & ~mask; - } - - // 切换位 - static inline RegType toggle(RegType reg) { - return reg ^ mask; - } - - // 测试位 - static inline bool is_set(RegType reg) { - return (reg & mask) != 0; - } -}; - -// 使用场景:GPIO配置 -using Pin5 = BitMask; - -uint32_t gpio_mode = 0; -gpio_mode = Pin5::set(gpio_mode); // 设置第5位 -if (Pin5::is_set(gpio_mode)) { - // 第5位已设置 -} -``` - -This code: - -- **Is type-safe**: The bit index is guaranteed to be valid at compile time -- **Has zero overhead**: All functions will be inlined into single instructions -- **Is self-documenting**: `Pin5::set()` is much clearer than `gpio_mode |= (1 << 5)` - ------- - -## Summary - -Templates are a core feature of modern C++ that: - -1. **Provide type-safe generic programming**: Avoiding the dangers of macros -2. **Enable zero-overhead abstraction**: Compile-time generation yields performance identical to hand-written code -3. **Support compile-time computation**: Moving runtime work forward to compile time -4. **Serve as modern C++ infrastructure**: The standard library and STL are built on top of templates - -For embedded developers, templates are particularly well-suited for: - -- Compile-time determined configurations -- Type-safe peripheral wrappers -- Zero heap allocation data structures -- Performance-critical algorithm specialization - -**In the next chapter**, we will dive into **function templates**, exploring core mechanisms like template argument deduction, return type deduction, and overload resolution, and implement a generic `min/max/clamp` function family. diff --git a/documents/en/vol4-advanced/01-function-templates.md b/documents/en/vol4-advanced/01-function-templates.md deleted file mode 100644 index 7871e38a2..000000000 --- a/documents/en/vol4-advanced/01-function-templates.md +++ /dev/null @@ -1,855 +0,0 @@ ---- -chapter: 12 -cpp_standard: -- 11 -- 14 -- 17 -- 20 -description: In-Depth Understanding of C++ Function Template Deduction Rules and Practical - Techniques -difficulty: intermediate -order: 1 -platform: host -prerequisites: -- 'Chapter 12: 模板入门概述' -reading_time_minutes: 24 -tags: -- cpp-modern -- host -- intermediate -title: Detailed Explanation of Function Templates ---- -# Modern C++ for Embedded Systems Tutorial—A Deep Dive into Function Templates - -Function templates are the starting point of C++ generic programming, allowing us to write a single piece of code that handles multiple types. But do you truly understand how the compiler deduces template parameters? Why does deduction sometimes fail? What is the difference between `auto` and template argument deduction? - -In this chapter, we will explore the internal mechanisms of function templates and implement a type-safe `min/max/clamp` function family. - ------- - -## Function Template Basic Syntax - -### Basic Form - -A function template begins with `template<...>`, followed by the function declaration: - -```cpp -template -T max(const T& a, const T& b) { - return a > b ? a : b; -} - -// 使用 -int x = max(5, 10); // T推导为int -double d = max(3.14, 2.71); // T推导为double -``` - -**Key points**: - -- `typename T` declares a type template parameter `T` -- The `typename` keyword can be replaced with `class` (but `typename` is recommended) -- The compiler automatically deduces the type of `T` based on the argument types - -### Multiple Template Parameters - -```cpp -template -auto add(T a, U b) -> decltype(a + b) { - return a + b; -} - -// 使用 -int x = 5; -double y = 3.14; -auto result = add(x, y); // 返回double -``` - -**Note**: `T` and `U` are deduced independently and may result in different types. - -### Non-Type Template Parameters - -In addition to types, template parameters can also be compile-time constants: - -```cpp -template -std::size_t array_size(T (&arr)[N]) { - return N; // 编译期获取数组大小 -} - -int data[42]; -std::size_t size = array_size(data); // 返回42,且是编译期常量 -``` - -This is particularly useful in embedded systems—we can safely obtain the size of an array without it decaying into a pointer. - ------- - -## Template Argument Deduction Rules - -### Rule 1: Exact Match Principle - -The compiler looks for the "best match" template parameter type without considering implicit conversions: - -```cpp -template -void process(T value); - -process(42); // T推导为int -process(3.14); // T推导为double -process('a'); // T推导为char - -// 但这不会工作: -process(42, 3.14); // 错误:只有一个T,无法同时匹配int和double -``` - -### Rule 2: References Are Ignored (By Default) - -By default, template argument deduction ignores references and top-level const: - -```cpp -template -void func(T arg); - -int x = 42; -const int& cref = x; - -func(x); // T推导为int -func(cref); // T推导为int(const和引用都被忽略) - -// 如果想保留引用和const: -template -void func_const(const T& arg); - -func_const(cref); // T推导为int,但参数类型是const int& -``` - -**Remember**: - -- `T` deduces the "type after removing references and top-level const" -- `const T&` preserves reference semantics -- `T&&` is a universal reference (detailed later) - -### Rule 3: Array Decay to Pointer - -```cpp -template -void func(T arg); - -int arr[10]; - -func(arr); // T推导为int*(数组退化为指针) - -// 如果想保留数组类型: -template -void func(T (&arr)[N]); - -int arr[10]; -func(arr); // T推导为int,N推导为10 -``` - -### Rule 4: Function Decay to Function Pointer - -```cpp -template -void func(T arg); - -void some_func(int); - -func(some_func); // T推导为void(*)(int) - -// 保留函数类型: -template -void func_ref(T& arg); - -func_ref(some_func); // T推导为void(int) -``` - -### Practical Deduction Table - -| Argument Type | `T` | `const T&` | `T&&` | -|----------|-----|-----------|-------| -| `int` | `int` | `int` | `int&&` | -| `const int` | `int` | `const int` | `const int&&` | -| `int&` | `int` | `const int&` | `int&` | -| `const int&` | `int` | `const int&` | `const int&` | -| `int&&` | `int` | `const int&` | `int&&` | - -**Important**: `T&&` deduces to an rvalue reference only when the argument is an rvalue; otherwise, it deduces to an lvalue reference (reference collapsing rules). - ------- - -## Trailing Return Types - -The trailing return type introduced in C++11 solves the problem of "return types depending on parameter types": - -### Problem Scenario - -```cpp -// ❌ 错误:T在返回类型时还未推导 -template -T add(T a, U b) { - return a + b; // 如果T是int,U是double,返回值截断 -} - -// ✅ 正确:使用尾随返回类型 -template -auto add(T a, U b) -> decltype(a + b) { - return a + b; // 返回decltype(a + b)的类型 -} -``` - -### C++14 Simplification: Return Type Deduction - -C++14 allows using `auto` directly as the return type, with the compiler deducing it automatically: - -```cpp -template -auto add(T a, U b) { - return a + b; // 推导为decltype(a + b) -} -``` - -### Advantages of Trailing Return Types - -1. **Can access function parameters**: - - ```cpp - template - auto deref(T iter) -> decltype(*iter) { - return *iter; // 返回解引用结果的类型 - } - ``` - -2. **Better suited for complex expressions**: - - ```cpp - template - auto multiply(T t, U u) -> decltype(t * u) { - return t * u; - } - ``` - -3. **Cleaner syntax** (for complex return types): - - ```cpp - // 传统写法(难读) - std::map::iterator func(int x); - - // 尾随返回类型(清晰) - auto func(int x) -> std::map::iterator; - ``` - -### decltype(auto): Perfect Forwarding of Return Values - -`decltype(auto)` introduced in C++14 combines the conciseness of `auto` with the precision of `decltype`: - -```cpp -template -struct Container { - T data[100]; - - // auto:返回T(拷贝) - auto get1(std::size_t i) { - return data[i]; - } - - // decltype(auto):返回T&(引用) - decltype(auto) get2(std::size_t i) { - return (data[i]); // 注意括号! - } -}; -``` - -**Key difference**: Parentheses cause `decltype` to return a reference type! - -```cpp -int x = 42; -decltype(x) a = 10; // int -decltype((x)) b = x; // int&(括号让表达式变成引用) -``` - ------- - -## Template Overloading and Specialization - -### Function Template Overloading - -Function templates can be overloaded with regular functions or other templates: - -```cpp -// 模板版本 -template -T max(T a, T b) { - return a > b ? a : b; -} - -// 针对const char*的特化(实际上是重载) -const char* max(const char* a, const char* b) { - return std::strcmp(a, b) > 0 ? a : b; -} - -// 使用 -max(5, 10); // 调用模板,T=int -max("hello", "world"); // 调用const char*重载 -``` - -### Overload Resolution Order - -The compiler selects in the following order: - -1. **Exact match regular function** -2. **Exact match template function** -3. **Conversion required regular function** -4. **Conversion required template function** - -```cpp -template -void func(T t); - -void func(int t); - -func(42); // 调用普通函数void func(int),优先级更高 -func(3.14); // 调用模板void func -``` - -### The Truth About Function Template "Specialization" - -**Important**: Function templates do not support true specialization; it can only be achieved through overloading! - -```cpp -// 主模板 -template -void process(T t) { - std::cout << "Generic: " << t << '\n'; -} - -// ❌ 这不是特化,是重载! -template<> -void process(int t) { - std::cout << "Int: " << t << '\n'; -} - -// ✅ 正确的"特化"方式:使用SFINAE或重载 -void process(int t) { - std::cout << "Int (overload): " << t << '\n'; -} -``` - -**Recommendation**: Prefer overloading over specialization for function templates. Specialization is primarily intended for class templates. - ------- - -## Universal References and Perfect Forwarding - -### Universal Reference - -When `T&&` appears in a template argument deduction context, it can be either an lvalue reference or an rvalue reference: - -```cpp -template -void wrapper(T&& arg) { // 万能引用 - // ... -} - -int x = 42; -wrapper(x); // T推导为int&,参数类型为int&(左值引用) -wrapper(42); // T推导为int,参数类型为int&&(右值引用) -``` - -**Identification rule**: It is a universal reference only when `T` is a deduced template parameter and the type is `T&&`. - -```cpp -template -class MyClass { - void func1(T&& arg); // ❌ 不是万能引用(T是类模板参数) - void func2(auto&& arg); // ✅ 是万能引用(C++20) -}; - -void func(auto&& arg); // ✅ 是万能引用(C++20) -``` - -### Reference Collapsing Rules - -When template argument deduction involves multiple layers of references, reference collapsing rules apply: - -| T | arg declaration | Final type | -|---|---------|----------| -| `int` | `T&&` | `int&&` | -| `int&` | `T&&` | `int&` | -| `int&&` | `T&&` | `int&&` | - -Simple memory aid: **The result is an rvalue reference only when both are rvalue references; otherwise, it is an lvalue reference.** - -### std::forward: Preserving Value Categories - -```cpp -template -void wrapper(T&& arg) { - target(std::forward(arg)); // 完美转发 -} - -template -void target(T&& arg); - -int x = 42; -wrapper(x); // 转发为左值 -wrapper(42); // 转发为右值 -``` - -The implementation principle of `std::forward`: - -```cpp -template -T&& forward(std::remove_reference_t& arg) { - return static_cast(arg); -} - -// 当T=int&时:返回int& -// 当T=int时:返回int&& -``` - ------- - -## Hands-on: Implementing the min/max/clamp Function Family - -Let's use what we've learned to implement a type-safe function family: - -### Basic Version - -```cpp -template -constexpr T min(const T& a, const T& b) { - return a < b ? a : b; -} - -template -constexpr T max(const T& a, const T& b) { - return a > b ? a : b; -} - -template -constexpr T clamp(const T& value, const T& low, const T& high) { - return (value < low) ? low : (value > high) ? high : value; -} -``` - -### Initializer List Version (Handling Multiple Arguments) - -```cpp -template -constexpr T min(std::initializer_list list) { - T result = *list.begin(); - for (auto item : list) { - if (item < result) result = item; - } - return result; -} - -// 使用 -int m = min({5, 2, 8, 1, 9}); // 返回1 -``` - -### Comparator Support Version (Similar to std:: Versions) - -```cpp -template -constexpr const T& min(const T& a, const T& b, Compare comp) { - return comp(a, b) ? a : b; -} - -// 使用 -auto greater_min = min(5, 10, std::greater<>{}); // 返回10 -``` - -### Embedded Optimized Version - -In embedded systems, we might need to avoid branches to improve performance: - -```cpp -template -constexpr T min_branchless(const T& a, const T& b) { - // 注意:这只对整数类型有效,且假设没有溢出 - return a < b ? a : b; // 编译器通常能优化为cmov指令 -} - -// 或者使用位运算(仅无符号整数) -template -constexpr T min_bitwise(const T& a, const T& b) { - static_assert(std::is_unsigned_v, "Only for unsigned types"); - return b ^ ((a ^ b) & -(a < b)); -} - -// 使用场景:信号处理、实时控制 -uint16_t sample = min_bitwise(raw_sample, threshold); -``` - -### Type-Safe clamp (With Compile-Time Checks) - -```cpp -template -constexpr T clamp(const T& value, const T& low, const T& high) { - static_assert(low <= high, "clamp: low must be <= high"); - return (value < low) ? low : (value > high) ? high : value; -} - -// 编译期检查 -constexpr auto result = clamp(5, 0, 10); // OK -// constexpr auto error = clamp(5, 10, 0); // 编译错误! -``` - -### Complete Implementation (Comprehensive Version) - -```cpp -template -constexpr const T& clamp(const T& value, const T& low, const T& high) { - static_assert(low <= high, "clamp: low must be <= high"); - return (value < low) ? low : (value > high) ? high : value; -} - -// 版本2:支持自定义比较器 -template -constexpr const T& clamp(const T& value, const T& low, const T& high, Compare comp) { - return comp(value, low) ? low : comp(high, value) ? high : value; -} - -// 版本3:返回值而非引用(避免临时对象问题) -template -constexpr T clamp_value(T value, T low, T high) { - return (value < low) ? low : (value > high) ? high : value; -} -``` - -### Usage Examples - -```cpp -// 传感器数值限制 -int16_t sensor_value = read_sensor(); -int16_t limited = clamp(sensor_value, -1000, 1000); - -// PWM占空比限制 -uint8_t duty = clamp(calculated_duty, 0, 255); - -// 浮点数限制 -float frequency = clamp(target_freq, 1000.0f, 5000.0f); -``` - ------- - -## Embedded Tip: Avoiding Code Bloat - -The main issue with templates in embedded development is code bloat. Every template instantiation generates a copy of the code, causing Flash usage to grow rapidly. - -### Technique 1: Use a Common Base Class - -```cpp -// ❌ 代码膨胀:每个类型都生成完整代码 -template -class Buffer { - T data[100]; - void clear() { /* 100行代码 */ } - void process() { /* 50行代码 */ } -}; - -// ✅ 优化:将类型无关部分提取到基类 -class BufferBase { -protected: - void clear_impl(void* data, std::size_t size); - void process_impl(void* data, std::size_t size); -}; - -template -class Buffer : private BufferBase { - T data[100]; -public: - void clear() { clear_impl(data, sizeof(data)); } - void process() { process_impl(data, sizeof(data)); } -}; -``` - -### Technique 2: extern template Explicit Instantiation - -C++11 allows declaring templates in header files and explicitly instantiating them in source files: - -```cpp -// header.h -template -void heavy_function(T t); - -// header.tpp(实现) -template -void heavy_function(T t) { - /* 大量代码 */ -} - -// header.cpp(显式实例化) -extern template void heavy_function; -extern template void heavy_function; -extern template void heavy_function; - -template void heavy_function; -template void heavy_function; -template void heavy_function; -``` - -This prevents other translation units from repeatedly instantiating these types. - -### Technique 3: Type Erasure - -For scenarios that do not require compile-time type information, use type erasure: - -```cpp -// ❌ 每种传感器类型都生成一份代码 -template -void process_sensor(Sensor& s) { - s.read(); - s.calibrate(); - // ... 大量代码 -} - -// ✅ 使用接口+虚函数 -class ISensor { -public: - virtual void read() = 0; - virtual void calibrate() = 0; - // ... -}; - -void process_sensor(ISensor& s) { - s.read(); - s.calibrate(); - // 只有一份代码 -} -``` - -### Technique 4: Limit the Number of Template Specializations - -```cpp -// ❌ 对每种配置都生成代码 -template -class Config; - -// ✅ 只对常用配置特化 -extern template class Config; -extern template class Config; -extern template class Config; -``` - -### Technique 5: Use `constexpr` + Type Selection - -```cpp -// 只在编译期生成需要的版本 -template -class FixedBuffer { - static_assert(Size <= 256, "Buffer too large"); - // ... 编译期确定大小 -}; - -// 而不是运行时分支 -void buffer(size_t size); // 需要处理所有大小 -``` - -### Code Bloat Detection Tools - -- **Compiler output**: Check the generated assembly or object file sizes -- **map file**: Analyze the symbol table to find duplicate code -- **nm/size commands**: Compare binary sizes across different configurations - -```bash -# 查看符号大小 -nm --size-sort output.elf | head -20 - -# 查看段大小 -size output.elf -``` - ------- - -## Common Pitfalls and Solutions - -### Pitfall 1: Deduction Failure - -```cpp -template -void func(T a, T b); - -func(42, 3.14); // ❌ 错误:T无法同时匹配int和double - -// 解决方案1:显式指定 -func(42, 3.14); - -// 解决方案2:两个模板参数 -template -void func(T a, U b); - -// 解决方案3:使用通用类型 -template -void func(T a, decltype(T{} + b) b); -``` - -### Pitfall 2: Returning a Reference to a Temporary Object - -```cpp -template -decltype(auto) get_first(const T& container) { - return container[0]; // ❌ 返回临时对象的引用! -} - -// ✅ 正确做法 -template -decltype(auto) get_first(T& container) { - return container[0]; // 返回引用 -} -``` - -### Pitfall 3: `auto` Return Type Losing References - -```cpp -template -auto get_element(T& container, std::size_t index) { - return container[index]; // ❌ 返回拷贝而非引用 -} - -// ✅ 使用decltype(auto) -template -decltype(auto) get_element(T& container, std::size_t index) { - return container[index]; // ✅ 返回引用 -} -``` - -### Pitfall 4: Confusing SFINAE with Hard Errors - -```cpp -template -auto func(T t) -> decltype(t.some_method()) { - return t.some_method(); -} - -func(42); // ❌ 硬错误:int没有some_method - // ✅ SFINAE场景:只是移除候选函数 -``` - -Correct SFINAE requires `std::enable_if` or C++17's `if constexpr`: - -```cpp -template -std::enable_if_t, T> func(T t) { - return t + 1; -} - -// 或C++17风格 -template -auto func(T t) { - if constexpr (std::is_integral_v) { - return t + 1; - } else { - return t; - } -} -``` - ------- - -## New Features in C++14/17/20 - -### C++14: Function Return Type Deduction - -```cpp -// C++11需要尾随返回类型 -template -auto add(T t, U u) -> decltype(t + u) { - return t + u; -} - -// C++14可以直接用auto -template -auto add(T t, U u) { - return t + u; -} -``` - -### C++17: Class Template Argument Deduction (CTAD) - -Although primarily used for class templates, this also affects function templates: - -```cpp -template -void process(std::vector vec); - -std::vector v{1, 2, 3}; // C++17 CTAD -process(v); // T自动推导为int -``` - -### C++17: if constexpr - -Simplifies conditional compilation inside templates: - -```cpp -template -void process(T t) { - if constexpr (std::is_integral_v) { - // 整数分支 - } else if constexpr (std::is_floating_point_v) { - // 浮点分支 - } else { - // 其他分支 - } -} -``` - -### C++20: Constraints and Abbreviated Function Templates - -```cpp -// 传统写法 -template -void func(T t) { - static_assert(std::is_integral_v); -} - -// C++20 Concepts -template -void func(T t); // 更清晰的约束 - -// 缩写函数模板 -void func(std::integral auto t); // 等价于上面 -``` - -### C++20: Template Syntax Improvements - -```cpp -// 类模板参数可以作为类型名 -template -struct Container { - T value; - Container(T value) : value(value) {} - - // C++20之前 - // Container operator+(const Container& other); - - // C++20:省略 - Container operator+(const Container& other); -}; -``` - ------- - -## Summary - -Function templates are the foundation of C++ generic programming: - -| Feature | Description | Use Case | -|------|------|----------| -| Template argument deduction | Compiler automatically deduces the type of T | Simplifies function calls | -| Trailing return type | Return type depends on parameter types | Complex type calculations | -| Universal reference | T&& can be an lvalue or rvalue reference | Perfect forwarding | -| Perfect forwarding | std::forward preserves value categories | Forwarding functions | -| Template overloading | Coexists with regular functions | Type-specific handling | - -**Practical recommendations**: - -1. **Prefer the `auto` return type** (C++14+), unless you need precise control -2. **Use `decltype(auto)` when forwarding is needed** to preserve reference semantics -3. **Use `T&&` + `std::forward` for perfect forwarding**, do not use `T&&` directly -4. **Implement function specializations via overloading**; true specialization is meant for class templates -5. **Watch out for code bloat in embedded systems**, use explicit instantiation or type erasure to control it - -**In the next chapter**, we will explore **class templates**, learn how to implement generic containers, understand the special rules for template member functions, and implement a fixed-capacity ring buffer. diff --git a/documents/en/vol4-advanced/02-class-templates.md b/documents/en/vol4-advanced/02-class-templates.md deleted file mode 100644 index 2b1243c30..000000000 --- a/documents/en/vol4-advanced/02-class-templates.md +++ /dev/null @@ -1,1914 +0,0 @@ ---- -chapter: 12 -cpp_standard: -- 11 -- 14 -- 17 -- 20 -description: In-depth Understanding of C++ Class Template Declaration, Instantiation, - and Embedded Applications -difficulty: intermediate -order: 2 -platform: host -prerequisites: -- 'Chapter 12.1: 函数模板详解' -reading_time_minutes: 51 -tags: -- cpp-modern -- host -- intermediate -title: Detailed Explanation of Class Templates ---- -# Modern C++ for Embedded Systems Tutorial — A Deep Dive into Class Templates - -Class templates are the core mechanism of C++ generic programming, allowing us to write type-agnostic class definitions. Standard library containers (such as `std::vector`, `std::array`), smart pointers (such as `std::unique_ptr`), and tuples (`std::tuple`) are all typical applications of class templates. - -For embedded developers, class templates are especially important—they let us determine types and sizes at compile time, avoid heap allocation, and achieve zero-overhead abstraction. - -This chapter will delve into the declaration and definition of class templates, member function templates, the limitations of virtual functions and templates, and ultimately implement a practical fixed-capacity ring buffer. - ------- - -## Class Template Basic Syntax - -### Basic Declaration Form - -A class template begins with `template<...>`, followed by the class definition: - -```cpp -template -class Stack { -private: - T data[100]; - std::size_t top_index = 0; -public: - void push(const T& item) { - data[top_index++] = item; - } - - T pop() { - return data[--top_index]; - } - - bool empty() const { - return top_index == 0; - } -}; - -// 使用 -Stack int_stack; -Stack float_stack; - -int_stack.push(42); -float_stack.push(3.14f); -``` - -**Key points**: - -- `template` declares a type template parameter `T` -- When using a class template, we must explicitly specify the template arguments: `Stack` -- Each different combination of template arguments generates an independent class - -### Multiple Type Parameters - -```cpp -template -class StaticMap { -private: - struct Entry { - Key key; - Value value; - bool occupied = false; - }; - Entry entries[Capacity]; -public: - bool insert(const Key& key, const Value& value) { - for (auto& entry : entries) { - if (!entry.occupied) { - entry.key = key; - entry.value = value; - entry.occupied = true; - return true; - } - } - return false; // 已满 - } - - bool find(const Key& key, Value& out_value) const { - for (const auto& entry : entries) { - if (entry.occupied && entry.key == key) { - out_value = entry.value; - return true; - } - } - return false; - } -}; - -// 使用 -StaticMap sensor_readings; -sensor_readings.insert("temperature", 25); -sensor_readings.insert("humidity", 60); -``` - -This multi-parameter design is highly practical in embedded systems, allowing us to determine container capacity at compile time. - -### Non-Type Template Parameters - -In addition to types, template parameters can also be compile-time constants: - -```cpp -template -class FixedArray { -private: - T data_[N]; -public: - constexpr std::size_t size() const { return N; } - - T& operator[](std::size_t index) { - return data_[index]; - } - - const T& operator[](std::size_t index) const { - return data_[index]; - } -}; - -// 使用 -FixedArray channel_values; -FixedArray adc_buffer; -``` - -**Restrictions on non-type template parameters**: - -- Must be a compile-time constant -- Type restrictions: integers, enumerations, pointers, references, `std::nullptr_t`, and C++20 floating-point types and literal types -- For pointers/references, the address must be of an object with static storage duration - -### Default Template Arguments - -Class templates can specify default values for template parameters: - -```cpp -template -class RingBuffer { -private: - T data_[N]; - std::size_t read_pos_ = 0; - std::size_t write_pos_ = 0; -public: - // ... -}; - -// 使用 -RingBuffer buffer1; // 容量32 -RingBuffer buffer2; // 容量16(使用默认值) -``` - -**Rules for default values**: - -- Once a default value is set for a parameter, all subsequent parameters must also have default values -- Function template default arguments (introduced in C++11) follow the same rules - -```cpp -template // OK -class Array; - -template // ERROR: T没有默认值 -class Array; -``` - ------- - -## Template Parameter Naming Conventions - -### typename vs class - -In template parameter declarations, `typename` and `class` are interchangeable: - -```cpp -template class Container1; // 推荐 -template class Container2; // 也可以 - -template class Both; // 混用 -``` - -**We recommend using `typename`** because: - -- The semantics are clearer (it is a type, not necessarily a class) -- It is consistent with the use of `typename` inside templates - -### Common Naming Conventions - -| Convention | Meaning | Example | -|------|------|------| -| `T` | Single type parameter | `template` | -| `T, U` | Multiple type parameters | `template` | -| `Key`, `Value` | Container-related | `template` | -| `N`, `Size` | Size parameters | `template` | -| `Fn` | Function/callable object | `template` | -| `IntType` | Integer type | `template` | - -```cpp -// 良好的命名示例 -template -class StaticMap { }; - -template -class AtomicInt { }; - -template -class Span { }; -``` - ------- - -## Member Function Templates - -Member functions of a class template can themselves be templates: - -### Member Function Template Basics - -```cpp -template -class Container { -private: - T data_; - -public: - // 普通构造函数 - explicit Container(const T& data) : data_(data) {} - - // 成员函数模板 - template - U convert_to() const { - return static_cast(data_); - } - - // 成员函数模板(多个模板参数) - template - U transform_with(Fn fn) const { - return fn(data_); - } -}; - -// 使用 -Container c(42); -double d = c.convert_to(); // U推导为double -std::string s = c.transform_with( - [](int x) { return std::to_string(x); } -); -``` - -### Specialization of Member Function Templates - -Member function templates can be fully specialized for specific types: - -```cpp -template -class Printer { -public: - void print(const T& value) { - std::cout << value << '\n'; - } - - // 成员函数模板特化(C++17之前需要全特化) - template - void print_special(const U& value); - - // 针对bool的特化 - template<> - void print_special(bool value) { - std::cout << (value ? "true" : "false") << '\n'; - } -}; - -// 使用 -Printer p; -p.print(42); -p.print_special(true); // 输出 "true" -``` - -**Note**: Member function templates do not support partial specialization, only full specialization. - -### Constructor Templates - -Constructor templates are commonly used for type conversion: - -```cpp -template -class Box { -private: - T value_; - -public: - // 普通构造函数 - explicit Box(const T& value) : value_(value) {} - - // 构造函数模板(允许从其他类型构造) - template - explicit Box(const Box& other) : value_(other.get()) {} - - T get() const { return value_; } -}; - -// 使用 -Box int_box(42); -Box double_box = int_box; // 使用构造函数模板 -``` - -**Important**: A constructor template does not prevent the compiler from generating a copy constructor! - -```cpp -Box box1(42); -Box box2 = box1; // 调用拷贝构造函数,而非模板构造函数 -``` - -### Assignment Operator Templates - -```cpp -template -class Optional { -private: - T* value_ = nullptr; - -public: - // 赋值运算符模板 - template - Optional& operator=(const Optional& other) { - if (other.value_) { - if (!value_) value_ = new T; - *value_ = static_cast(*other.value_); - } else { - delete value_; - value_ = nullptr; - } - return *this; - } - - ~Optional() { delete value_; } -}; -``` - ------- - -## Limitations of Virtual Functions and Templates - -### Rule 1: Virtual functions cannot be function templates - -```cpp -class Base { -public: - // ❌ 错误:虚函数不能是模板 - template - virtual void process(T value) {} - - // ✅ 正确:普通虚函数 - virtual void process(int value) {} -}; -``` - -**Reason**: Virtual functions are implemented through a virtual function table (vtable), where each virtual function occupies a fixed position. Template functions only exist after instantiation, so the compiler cannot determine the vtable layout at compile time. - -### Rule 2: Class templates can have virtual functions - -```cpp -template -class Sensor { -public: - virtual ~Sensor() = default; - virtual T read() = 0; - virtual void calibrate() = 0; -}; - -// 使用 -class TemperatureSensor : public Sensor { -public: - float read() override { return 25.0f; } - void calibrate() override { } -}; -``` - -### Rule 3: Virtual member functions cannot have default template arguments - -```cpp -template -class Base { -public: - // ❌ 错误:虚函数模板参数默认值 - template - virtual void func(U arg) = 0; -}; -``` - -### Design suggestion: Use type erasure instead of virtual function templates - -If we need the effect of a "virtual function template," we can use type erasure: - -```cpp -// 方案1:使用std::function(动态类型擦除) -class Processor { -private: - std::function int_processor_; - std::function double_processor_; - std::function string_processor_; - -public: - template - void set_processor() { - if constexpr (std::is_same_v) { - int_processor_ = [](int x) { std::cout << "int: " << x << '\n'; }; - } else if constexpr (std::is_same_v) { - double_processor_ = [](double x) { std::cout << "double: " << x << '\n'; }; - } - } - - template - void process(T value) { - if constexpr (std::is_same_v) { - if (int_processor_) int_processor_(value); - } else if constexpr (std::is_same_v) { - if (double_processor_) double_processor_(value); - } - } -}; - -// 方案2:使用variant(C++17静态多态) -#include - -template -struct Visitor : Ts... { - using Ts::operator()...; -}; - -template -Visitor(Ts...) -> Visitor; - -class DynamicProcessor { -private: - using Value = std::variant; - std::vector data_; - -public: - template - void add(T value) { - data_.push_back(value); - } - - void process_all() { - Visitor visitor{ - [](int x) { std::cout << "int: " << x << '\n'; }, - [](double x) { std::cout << "double: " << x << '\n'; }, - [](const std::string& s) { std::cout << "string: " << s << '\n'; } - }; - for (auto& value : data_) { - std::visit(visitor, value); - } - } -}; -``` - -### Performance Trade-offs Between Virtual Functions and Templates - -| Feature | Virtual Functions | Templates | -|------|--------|------| -| Polymorphism type | Runtime | Compile time | -| Code size | Small (shared code) | Large (generates code per instance) | -| Call overhead | Indirect call (vtable lookup) | Direct call (can be inlined) | -| Type safety | Restricted by base class interface | Fully type-safe | -| Binary compatibility | Good (stable ABI) | Poor (requires recompilation) | - -**Embedded suggestions**: - -- Performance-critical paths: Use templates (compile-time polymorphism) -- Runtime configuration/plugin architectures: Use virtual functions (runtime polymorphism) -- Combining both: Use templates to generate concrete types, and manage them uniformly through a base class interface - ------- - -## Class Template Instantiation - -### Implicit Instantiation - -When we use a class template, the compiler automatically instantiates the required member functions: - -```cpp -template -class Counter { -private: - T count_ = 0; - -public: - void increment() { ++count_; } - void decrement() { --count_; } - T get() const { return count_; } -}; - -// 只使用了 increment 和 get -Counter c; -c.increment(); -auto value = c.get(); - -// decrement 不会被实例化(未被使用) -// 这意味着即使 decrement 有编译错误,也不会触发 -``` - -**Practical tip**: We can use this feature to implement conditional compilation: - -```cpp -template -class Container { -public: - void process() { - if constexpr (std::is_integral_v) { - // 整数特有操作 - } else { - // 其他类型操作 - } - } - - // 这个函数只对整数类型有效 - template - std::enable_if_t, U> get_bitmask() { - return static_cast(0xFF); - } -}; -``` - -### Explicit Instantiation - -We can explicitly tell the compiler to instantiate a specific type: - -```cpp -// header.h -template -class Vector { - // ... 大量代码 -}; - -// header.cpp -// 显式实例化(在cpp文件中) -template class Vector; -template class Vector; -template class Vector; - -// 使用时,这些类型的定义已经存在,不需要重新实例化 -``` - -**Benefits**: - -- Reduces compilation time -- Hides implementation details (template implementations can be placed in cpp files) -- Controls code bloat - -### extern template Declaration (C++11) - -Tells the compiler "this template has been instantiated elsewhere": - -```cpp -// my_vector.h -template -class Vector { - // ... 完整定义 -}; - -// my_vector.cpp -template class Vector; -template class Vector; - -// main.cpp -extern template class Vector; // 声明:Vector在别处实例化 -extern template class Vector; - -void use_vector() { - Vector v; // 使用已实例化的版本 -} -``` - -**Use case**: Reducing compilation time and code bloat in large projects. - ------- - -## Hands-on: Fixed-Capacity Ring Buffer - -Let's implement a practical, fixed-capacity ring buffer—a data structure commonly used in embedded systems for serial communication, sensor data acquisition, and other scenarios. - -### Design Goals - -1. **Zero heap allocation**: All storage is on the stack or in static storage -2. **Compile-time size determination**: Using non-type template parameters -3. **Type safety**: Supporting arbitrary element types -4. **Thread safety (optional)**: Providing configuration options for atomic operations -5. **Graceful full/empty handling**: Distinguishing between full and empty states - -### Basic Implementation - -```cpp -#include -#include -#include - -template -class RingBuffer { -private: - std::array data_; - std::size_t read_pos_ = 0; - std::size_t write_pos_ = 0; - bool full_ = false; - -public: - static constexpr std::size_t capacity = N; - - // 默认构造 - RingBuffer() = default; - - // 检查状态 - bool empty() const { - return !full_ && (read_pos_ == write_pos_); - } - - bool full() const { - return full_; - } - - std::size_t size() const { - if (full_) return N; - if (write_pos_ >= read_pos_) return write_pos_ - read_pos_; - return N + write_pos_ - read_pos_; - } - - // 写入元素 - bool push(const T& item) { - if (full_) { - return false; // 缓冲区满 - } - - data_[write_pos_] = item; - write_pos_ = (write_pos_ + 1) % N; - - if (write_pos_ == read_pos_) { - full_ = true; - } - - return true; - } - - bool push(T&& item) { - if (full_) { - return false; - } - - data_[write_pos_] = std::move(item); - write_pos_ = (write_pos_ + 1) % N; - - if (write_pos_ == read_pos_) { - full_ = true; - } - - return true; - } - - // 读取元素 - bool pop(T& item) { - if (empty()) { - return false; // 缓冲区空 - } - - item = data_[read_pos_]; - read_pos_ = (read_pos_ + 1) % N; - full_ = false; - - return true; - } - - // 查看首元素(不移除) - bool peek(T& item) const { - if (empty()) { - return false; - } - - item = data_[read_pos_]; - return true; - } - - // 清空缓冲区 - void clear() { - read_pos_ = 0; - write_pos_ = 0; - full_ = false; - } -}; -``` - -### Thread-Safe Version - -```cpp -template -class RingBufferTS; - -// 非线程安全版本 -template -class RingBufferTS : public RingBuffer { - using Base = RingBuffer; -public: - using Base::Base; -}; - -// 线程安全版本 -template -class RingBufferTS { -private: - std::array data_; - std::atomic read_pos_{0}; - std::atomic write_pos_{0}; - std::atomic full_{false}; - -public: - static constexpr std::size_t capacity = N; - - bool empty() const { - return !full_.load(std::memory_order_acquire) && - (read_pos_.load(std::memory_order_acquire) == - write_pos_.load(std::memory_order_acquire)); - } - - bool full() const { - return full_.load(std::memory_order_acquire); - } - - std::size_t size() const { - const auto rp = read_pos_.load(std::memory_order_acquire); - const auto wp = write_pos_.load(std::memory_order_acquire); - const bool f = full_.load(std::memory_order_acquire); - - if (f) return N; - if (wp >= rp) return wp - rp; - return N + wp - rp; - } - - bool push(const T& item) { - const auto wp = write_pos_.load(std::memory_order_relaxed); - const auto next_wp = (wp + 1) % N; - const auto rp = read_pos_.load(std::memory_order_acquire); - - if (next_wp == rp && full_.load(std::memory_order_acquire)) { - return false; // 满 - } - - data_[wp] = item; - write_pos_.store(next_wp, std::memory_order_release); - - if (next_wp == rp) { - full_.store(true, std::memory_order_release); - } - - return true; - } - - bool pop(T& item) { - const auto rp = read_pos_.load(std::memory_order_relaxed); - const auto wp = write_pos_.load(std::memory_order_acquire); - - if (rp == wp && !full_.load(std::memory_order_acquire)) { - return false; // 空 - } - - item = data_[rp]; - const auto next_rp = (rp + 1) % N; - read_pos_.store(next_rp, std::memory_order_release); - full_.store(false, std::memory_order_release); - - return true; - } -}; -``` - -### Embedded-Optimized Version - -For resource-constrained embedded environments, we can optimize further: - -```cpp -template -class CompactRingBuffer { -private: - // 使用位域优化:假设容量不超过255 - static_assert(N <= 256, "Capacity too large for compact storage"); - - T data_[N]; - uint8_t read_pos_ = 0; - uint8_t write_pos_ = 0; - uint8_t count_ = 0; // 使用计数而非full标志 - -public: - static constexpr std::size_t capacity = N; - - // 内联所有函数(嵌入式常用) - [[gnu::always_inline]] bool empty() const { - return count_ == 0; - } - - [[gnu::always_inline]] bool full() const { - return count_ == N; - } - - [[gnu::always_inline]] std::size_t size() const { - return count_; - } - - // 批量写入(DMA友好) - std::size_t push_batch(const T* items, std::size_t count) { - std::size_t written = 0; - - for (std::size_t i = 0; i < count && !full(); ++i) { - data_[write_pos_] = items[i]; - write_pos_ = static_cast((write_pos_ + 1) % N); - ++count_; - ++written; - } - - return written; - } - - // 批量读取(DMA友好) - std::size_t pop_batch(T* items, std::size_t count) { - std::size_t read = 0; - - for (std::size_t i = 0; i < count && !empty(); ++i) { - items[i] = data_[read_pos_]; - read_pos_ = static_cast((read_pos_ + 1) % N); - --count_; - ++read; - } - - return read; - } - - // 直接访问底层存储(用于DMA传输) - T* data() { return data_; } - const T* data() const { return data_; } - - // 获取连续空间大小(用于DMA) - std::size_t contiguous_write_space() const { - if (write_pos_ >= read_pos_) { - return N - write_pos_; - } - return read_pos_ - write_pos_ - 1; - } - - std::size_t contiguous_read_space() const { - if (read_pos_ > write_pos_) { - return N - read_pos_; - } - return write_pos_ - read_pos_; - } -}; -``` - -### Complete Implementation Example - -::: details Click to expand the complete RingBuffer implementation (with iterator support) - -```cpp -#include -#include -#include -#include - -template -class RingBuffer { -private: - std::array data_; - std::size_t read_pos_ = 0; - std::size_t write_pos_ = 0; - bool full_ = false; - -public: - static constexpr std::size_t capacity = N; - - // 类型定义 - using value_type = T; - using size_type = std::size_t; - using reference = T&; - using const_reference = const T&; - - // 构造函数 - RingBuffer() = default; - - // 迭代器支持 - class iterator { - private: - RingBuffer* buffer_; - std::size_t pos_; - bool ended_; - - public: - using iterator_category = std::forward_iterator_tag; - using value_type = T; - using difference_type = std::ptrdiff_t; - using pointer = T*; - using reference = T&; - - iterator(RingBuffer* buffer, std::size_t pos, bool ended) - : buffer_(buffer), pos_(pos), ended_(ended) {} - - reference operator*() { return buffer_->data_[pos_]; } - pointer operator->() { return &buffer_->data_[pos_]; } - - iterator& operator++() { - if (pos_ == buffer_->write_pos_) { - ended_ = true; - } else { - pos_ = (pos_ + 1) % N; - } - return *this; - } - - iterator operator++(int) { - iterator tmp = *this; - ++(*this); - return tmp; - } - - friend bool operator==(const iterator& a, const iterator& b) { - return a.buffer_ == b.buffer_ && - a.pos_ == b.pos_ && - a.ended_ == b.ended_; - } - - friend bool operator!=(const iterator& a, const iterator& b) { - return !(a == b); - } - }; - - class const_iterator { - private: - const RingBuffer* buffer_; - std::size_t pos_; - bool ended_; - - public: - using iterator_category = std::forward_iterator_tag; - using value_type = const T; - using difference_type = std::ptrdiff_t; - using pointer = const T*; - using reference = const T&; - - const_iterator(const RingBuffer* buffer, std::size_t pos, bool ended) - : buffer_(buffer), pos_(pos), ended_(ended) {} - - reference operator*() const { return buffer_->data_[pos_]; } - pointer operator->() const { return &buffer_->data_[pos_]; } - - const_iterator& operator++() { - if (pos_ == buffer_->write_pos_) { - ended_ = true; - } else { - pos_ = (pos_ + 1) % N; - } - return *this; - } - - const_iterator operator++(int) { - const_iterator tmp = *this; - ++(*this); - return tmp; - } - - friend bool operator==(const const_iterator& a, const const_iterator& b) { - return a.buffer_ == b.buffer_ && - a.pos_ == b.pos_ && - a.ended_ == b.ended_; - } - - friend bool operator!=(const const_iterator& a, const const_iterator& b) { - return !(a == b); - } - }; - - iterator begin() { return empty() ? end() : iterator(this, read_pos_, false); } - iterator end() { return iterator(this, write_pos_, true); } - const_iterator begin() const { return cbegin(); } - const_iterator end() const { return cend(); } - const_iterator cbegin() const { - return empty() ? cend() : const_iterator(this, read_pos_, false); - } - const_iterator cend() const { return const_iterator(this, write_pos_, true); } - - // 状态查询 - bool empty() const { return !full_ && (read_pos_ == write_pos_); } - bool full() const { return full_; } - std::size_t size() const { - if (full_) return N; - if (write_pos_ >= read_pos_) return write_pos_ - read_pos_; - return N + write_pos_ - read_pos_; - } - static constexpr std::size_t capacity() { return N; } - - // 元素访问 - bool push(const T& item) { - if (full_) return false; - data_[write_pos_] = item; - advance_write(); - return true; - } - - bool push(T&& item) { - if (full_) return false; - data_[write_pos_] = std::move(item); - advance_write(); - return true; - } - - template - bool emplace(Args&&... args) { - if (full_) return false; - data_[write_pos_] = T(std::forward(args)...); - advance_write(); - return true; - } - - bool pop(T& item) { - if (empty()) return false; - item = std::move(data_[read_pos_]); - advance_read(); - return true; - } - - bool peek(T& item) const { - if (empty()) return false; - item = data_[read_pos_]; - return true; - } - - // 批量操作 - template - std::size_t push_range(InputIt first, InputIt last) { - std::size_t count = 0; - for (auto it = first; it != last && !full(); ++it) { - data_[write_pos_] = *it; - advance_write(); - ++count; - } - return count; - } - - // 清空 - void clear() { - read_pos_ = 0; - write_pos_ = 0; - full_ = false; - } - -private: - void advance_write() { - write_pos_ = (write_pos_ + 1) % N; - if (write_pos_ == read_pos_) { - full_ = true; - } - } - - void advance_read() { - read_pos_ = (read_pos_ + 1) % N; - full_ = false; - } -}; - -``` - -::: - -### Usage Examples - -```cpp - -// 串口接收缓冲区 -RingBuffer uart_rx_buffer; - -// 在串口中断中 -void UART_IRQHandler() { - if (UART->SR & UART_SR_RXNE) { - uint8_t data = UART->DR; - uart_rx_buffer.push(data); - } -} - -// 在主循环中处理 -void process_uart_data() { - uint8_t byte; - while (uart_rx_buffer.pop(byte)) { - // 处理接收到的数据 - protocol_parse(byte); - } -} - -// 传感器数据缓冲 -struct SensorData { - uint32_t timestamp; - float value; -}; - -RingBuffer sensor_buffer; - -// 采集数据 -void sample_sensor() { - SensorData data{ - .timestamp = get_system_tick(), - .value = read_adc() - }; - - if (!sensor_buffer.push(data)) { - // 缓冲区满,记录错误 - error_count++; - } -} - -// 批量发送 -void send_sensor_data() { - SensorData data; - while (sensor_buffer.pop(data)) { - send_to_host(data); - } -} -``` - ------- - -## Class Template Specialization - -### Full Specialization - -Providing a completely different implementation for specific template arguments: - -```cpp -// 主模板 -template -class FixedVector { -private: - T data_[N]; - std::size_t size_ = 0; -public: - // 通用实现 -}; - -// 针对bool的全特化(使用位压缩) -template -class FixedVector { -private: - static constexpr std::size_t byte_count = (N + 7) / 8; - uint8_t data_[byte_count]; - std::size_t size_ = 0; - -public: - void push_back(bool value) { - if (size_ < N) { - if (value) { - data_[size_ / 8] |= (1 << (size_ % 8)); - } - ++size_; - } - } - - bool get(std::size_t index) const { - return data_[index / 8] & (1 << (index % 8)); - } - - std::size_t size() const { return size_; } -}; - -// 使用 -FixedVector int_vec; // 使用主模板 -FixedVector bool_vec; // 使用特化版本(节省内存) -``` - -### Partial Specialization (Allowed for Class Templates) - -Specializing only a portion of the template parameters: - -```cpp -// 主模板 -template -class SmartPointer { - // ... -}; - -// 偏特化:针对void类型 -template -class SmartPointer { - // void特有实现 -}; - -// 主模板:两个类型参数 -template -class StaticMap { - // ... -}; - -// 偏特化:固定Value类型为int -template -class StaticMap { - // 针对int值的优化实现 -}; -``` - -**Important**: Function templates do not support partial specialization; only class templates do. - -### Conditional Specialization Using SFINAE - -```cpp -#include - -// 主模板 -template -class OptimizedBuffer { -private: - T data_[N]; -public: - // 通用实现 - void process() { - for (std::size_t i = 0; i < N; ++i) { - data_[i] = T{}; - } - } -}; - -// 针对算术类型的特化 -template -class OptimizedBuffer>> { -private: - T data_[N]; -public: - // 使用memset优化 - void process() { - std::memset(data_, 0, sizeof(data_)); - } -}; - -// 使用 -OptimizedBuffer int_buffer; // 使用memset版本 -OptimizedBuffer str_buffer; // 使用循环版本 -``` - -### Simplifying Specialization with C++17 using Declarations - -```cpp -template -using FastBuffer = RingBuffer; - -template -using LargeBuffer = RingBuffer; - -// 使用 -FastBuffer uart_tx; -LargeBuffer sensor_data; -``` - ------- - -## Debugging Tips: Understanding Template Instantiation Error Messages - -Template compilation errors are notorious for "information explosion." A simple mistake can generate hundreds of lines of error messages. Let's learn how to quickly pinpoint the problem. - -### Typical Error Message Structure - -```text -error: no matching function for call to 'foo' - foo(42); - ^~~ -note: candidate template ignored: deduced conflicting types for parameter 'T' ('int' vs 'double') - template - void foo(T a, T b); - ^ -``` - -Key information: - -1. **Error location**: The line where `foo(42)` is located -2. **Failure reason**: `deduced conflicting types` (type conflict) -3. **Candidate declarations**: The template declarations that were considered - -### Common Template Error Patterns - -#### Pattern 1: Type Deduction Failure - -```cpp -template -void process(T& a, T& b); - -int x; -double y; - -process(x, y); // 错误:T无法同时推导为int和double -``` - -**Error message characteristics**: - -```text -note: template argument deduction/substitution failed: -note: couldn't deduce template parameter 'T' -``` - -**Solution**: - -```cpp -// 方案1:显式指定类型 -process(x, y); - -// 方案2:两个模板参数 -template -void process(T& a, U& b); -``` - -#### Pattern 2: Member Function Access Failure - -```cpp -template -void process(T& value) { - value.some_method(); // T可能没有some_method -} -``` - -**Error message characteristics**: - -```text -error: no member named 'some_method' in 'X' -``` - -**Solution**: Use constraints or if constexpr - -```cpp -template -auto process(T& value) -> decltype(value.some_method(), void()) { - value.some_method(); -} - -// 或C++17 -template -void process(T& value) { - if constexpr (requires { value.some_method(); }) { - value.some_method(); - } -} -``` - -#### Pattern 3: Excessive Instantiation Depth - -```cpp -template -struct Factorial { - static constexpr int value = N * Factorial::value; -}; - -Factorial<1000000> x; // 递归太深 -``` - -**Error message characteristics**: - -```text -fatal error: template instantiation depth exceeds maximum of 900 -``` - -**Solution**: Use constexpr functions - -```cpp -constexpr int factorial(int n) { - return n <= 1 ? 1 : n * factorial(n - 1); -} -``` - -### Debugging Tips Summary - -| Tip | Description | Example | -|------|------|------| -| Use `static_assert` | Add constraints at the beginning of the template | `static_assert(std::is_integral_v)` | -| Check the error location | Scroll up to find the first error location | Look for "instantiated from" | -| Use `-fverbose-asm` | View the generated assembly | Understand template expansion results | -| Use `-ftime-report` | Analyze compilation time | Identify slow-compiling templates | -| Use C++20 Concepts | Constrain template parameters | `template` | - -### Simplifying Error Messages with Concepts (C++20) - -```cpp -// C++17之前:错误信息混乱 -template -void process(T t) { - static_assert(std::is_integral_v, "T must be integral"); - t += 1; -} - -// C++20:清晰的错误信息 -template -void process(T t) { - t += 1; -} - -// 如果调用 process(3.14),错误信息是: -// error: no matching function for call to 'process' -// note: candidate template ignored: constraints not satisfied -``` - -### Practical Debugging Macros - -```cpp -// 打印类型信息(用于调试) -template -struct print_type; - -// 使用时会导致编译错误,从而显示类型 -print_type x; - -// 错误信息会显示: -// error: implicit instantiation of undefined template 'print_type' -``` - -```cpp -// 编译期类型检查 -template -struct check_same { - static_assert(std::is_same_v, "Types must be the same"); - using type = T; -}; - -// 使用 -check_same{}; -``` - ------- - -## Embedded Practical Scenarios - -### Scenario 1: Register Mapping Template - -```cpp -template -class Register { -public: - // volatile确保读写不被优化 - inline RegisterType read() const { - return *reinterpret_cast(Address); - } - - inline void write(RegisterType value) const { - *reinterpret_cast(Address) = value; - } - - // 位操作 - void set_bits(RegisterType mask) const { - write(read() | mask); - } - - void clear_bits(RegisterType mask) const { - write(read() & ~mask); - } -}; - -// 使用 -using GPIOA_ODR = Register; -using GPIOA_MODER = Register; - -void led_on() { - GPIOA_ODR od; - od.set_bits(1 << 5); // 设置PA5 -} - -void led_off() { - GPIOA_ODR od; - od.clear_bits(1 << 5); // 清除PA5 -} -``` - -### Scenario 2: DMA Descriptor Template - -```cpp -template -class DMADescriptor { -private: - struct Descriptor { - T* source; - T* destination; - std::size_t count; - uint32_t config; - }; - - Descriptor desc_; - -public: - DMADescriptor(T* src, T* dst, uint32_t cfg = 0) - : desc_{src, dst, Count, cfg} {} - - const Descriptor* get() const { return &desc_; } - - // 链式传输 - template - DMADescriptor* chain(DMADescriptor& next) { - desc_.config |= reinterpret_cast(next.get()); - return &next; - } -}; - -// 使用 -uint8_t rx_buffer[256]; -uint8_t tx_buffer[256]; -DMADescriptor rx_desc(nullptr, rx_buffer); -DMADescriptor tx_desc(tx_buffer, nullptr); - -// 配置DMA -DMA->CH0.SAR = reinterpret_cast(rx_desc.get()); -``` - -### Scenario 3: Delay Template (Compile-Time Calculation) - -```cpp -template -class Delay { - static constexpr std::size_t cycles_per_us = Clock::frequency / 1000000; - static constexpr std::size_t total_cycles = DelayMicroseconds * cycles_per_us; - -public: - static inline void wait() { - // 编译期展开的循环 - for (volatile std::size_t i = 0; i < total_cycles; ++i) { - __NOP(); - } - } -}; - -// 使用 -struct SystemClock { - static constexpr std::size_t frequency = 72000000; // 72MHz -}; - -using Delay1ms = Delay; -using Delay10us = Delay; - -Delay1ms::wait(); // 编译期确定循环次数 -``` - -### Scenario 4: Type-Safe Peripheral IDs - -```cpp -template -class PeripheralID { -private: - static constexpr std::size_t base_address = get_base_address(Instance); - static constexpr std::size_t irq_number = get_irq_number(Instance); - -public: - static constexpr std::size_t base() { return base_address; } - static constexpr std::size_t irq() { return irq_number; } - - // 启用时钟 - static void enable_clock() { - auto rcc = Register{}; - rcc.set_bits(1 << Instance); - } -}; - -// 使用 -using UART1 = PeripheralID; -using UART2 = PeripheralID; - -void init_uart() { - UART1::enable_clock(); - auto uart_cr1 = Register{}; - uart_cr1.write(0x0000); // 配置UART -} -``` - ------- - -## Common Pitfalls and Solutions - -### Pitfall 1: Separating Template Definition and Declaration - -```cpp -// header.h -template -class MyClass { -public: - void func(); // 声明 -}; - -// source.cpp -template -void MyClass::func() { // ❌ 错误:非内联成员函数 - // 实现 -} -``` - -**Problem**: The compiler needs to see the complete template definition at the point of use. - -**Solution**: - -```cpp -// 方案1:全部放在头文件 -template -class MyClass { -public: - void func() { // ✅ 内联定义 - // 实现 - } -}; - -// 方案2:显式实例化 -// header.h -template -class MyClass { -public: - void func(); -}; - -// source.cpp -#include "header.h" - -template -void MyClass::func() { - // 实现 -} - -// 显式实例化常用类型 -template class MyClass; -template class MyClass; -``` - -### Pitfall 2: Name Lookup for Dependent Types - -```cpp -template -class MyClass { - typedef int value_type; // 或 using value_type = int; - - void func() { - value_type x = 0; // ❌ 错误:依赖类型需要typename - } -}; -``` - -**Solution**: - -```cpp -template -class MyClass { - typedef typename T::value_type value_type; - - void func() { - typename T::value_type x = 0; // ✅ 使用typename - } -}; -``` - -### Pitfall 3: Dependency on the this Pointer - -```cpp -template -class Base { -public: - void func() {} -}; - -template -class Derived : public Base { -public: - void method() { - func(); // ❌ 可能找不到func - this->func(); // ✅ 显式使用this - } -}; -``` - -### Pitfall 4: Template Friends - -```cpp -template -class MyClass { - // 让所有MyClass成为友元 - template - friend class MyClass; - -private: - T data_; -}; - -// 或者:让特定实例成为友元 -template -class Container { - friend void special_func<>(Container&); // 函数模板友元 -}; -``` - -### Pitfall 5: Mixing Virtual Functions and Templates - -```cpp -template -class Base { -public: - // ❌ 错误:虚函数不能是模板 - template - virtual void func(U arg) {} -}; -``` - -**Solution**: Use type erasure or overloading - -```cpp -// 方案1:类型擦除 -class Base { -public: - virtual ~Base() = default; - virtual void process_int(int) = 0; - virtual void process_double(double) = 0; -}; - -// 方案2:使用std::function -class DynamicProcessor { - std::function int_fn_; - std::function double_fn_; -public: - template - void register_handler() { - if constexpr (std::is_same_v) { - int_fn_ = [](int x) { /* 处理 */ }; - } - } -}; -``` - ------- - -## New Features in C++14/17/20 - -### C++14: Variable Templates - -```cpp -// 变量模板 -template -constexpr T pi = T(3.1415926535897932385); - -// 使用 -float f = pi; -double d = pi; - -// 嵌入式应用:寄存器地址 -template -constexpr std::size_t register_address = base_address + N * sizeof(T); - -// 使用 -auto uart_dr = Register>{}; -``` - -### C++14: Generic Lambdas - -```cpp -template -void process_container(T& container) { - // 使用auto&&参数的lambda - auto for_each = [](auto&& fn) { - return [&](auto&&... args) { - fn(std::forward(args)...); - }; - }; -} -``` - -### C++17: Class Template Argument Deduction (CTAD) - -```cpp -// 不再需要指定模板参数 -std::pair p(42, "hello"); // 推导为pair -std::vector v{1, 2, 3}; // 推导为vector -std::array a{1, 2, 3, 4}; // 推导为array - -// 自定义类型也可以 -template -struct Buffer { - Buffer(T (&arr)[N]) : /* ... */ {} -}; - -Buffer buf = {1, 2, 3}; // 推导为Buffer -``` - -### C++17: if constexpr - -Simplifying conditional code inside templates: - -```cpp -template -auto serialize(T value) { - if constexpr (std::is_integral_v) { - return std::to_string(value); - } else if constexpr (std::is_floating_point_v) { - return std::to_string(value); - } else if constexpr (std::is_same_v) { - return value; - } else { - return std::string("[unknown]"); - } -} -``` - -### C++17: inline Variables - -```cpp -template -class Counter { -public: - static inline std::size_t count = 0; // C++17:头文件中定义 -}; - -// 不需要在cpp文件中定义! -``` - -### C++17: Fold Expressions - -Simplifying variadic templates: - -```cpp -// 模板递归写法(C++14) -template -auto sum(Args... args) { - return (args + ...); // C++17折叠表达式 -} - -// 嵌入式应用:多通道初始化 -template -void init_pins(Pins... pins) { - (init_pin(pins), ...); // 对每个引脚调用init_pin -} - -// 使用 -init_pins(GPIOA_5, GPIOA_6, GPIOB_0); -``` - -### C++20: Concept Constraints - -```cpp -// 定义概念 -template -concept Integral = std::is_integral_v; - -template -concept SignedIntegral = Integral && std::is_signed_v; - -// 使用概念 -template -T abs(T value) { - return value < 0 ? -value : value; -} - -// 缩写函数模板 -void process(Integral auto value) { - // ... -} - -// 嵌入式应用:外设概念 -template -concept UART = requires(T t) { - { t.read() } -> std::convertible_to; - { t.write(uint8_t{}) } -> std::same_as; -}; - -template -void send_string(U& uart, const char* str) { - while (*str) uart.write(*str++); -} -``` - -### C++20: requires Expressions - -```cpp -template -concept Serializable = requires(T t) { - { t.serialize() } -> std::convertible_to>; - { t.deserialize(std::declval>()) }; -}; - -// 使用 -template -void send_object(T& obj) { - auto data = obj.serialize(); - // 发送数据 -} -``` - ------- - -## Summary - -Class templates are the core tool of C++ generic programming, and they are especially important in embedded development: - -| Feature | Embedded Advantage | Application Scenario | -|------|-----------|----------| -| Compile-time size determination | Zero heap allocation | Fixed-capacity containers | -| Type parameterization | Code reuse | Communication protocols, data structures | -| Non-type parameters | Constant expressions | Register mapping, buffers | -| Compile-time polymorphism | Zero-overhead abstraction | Algorithm specialization, strategy pattern | -| Inline optimization | Reduced function calls | Hot path code | - -**Practical advice**: - -1. **Prefer class templates over macros**: They provide type safety and are debuggable -2. **Use non-type template parameters judiciously**: Determine sizes at compile time to avoid dynamic allocation -3. **Watch out for code bloat**: Control it through shared base classes and explicit instantiation -4. **Leverage C++17/20 features**: `if constexpr` and Concepts make template code clearer -5. **Keep virtual functions and templates separate**: Use virtual functions for runtime polymorphism, and templates for compile-time polymorphism - -**In the next chapter**, we will explore **template metaprogramming**, learning advanced techniques like type traits, SFINAE (Substitution Failure Is Not An Error), and variadic templates to implement compile-time algorithm selection and type computation. diff --git a/documents/en/vol4-advanced/03-template-specialization.md b/documents/en/vol4-advanced/03-template-specialization.md deleted file mode 100644 index 33a0639ef..000000000 --- a/documents/en/vol4-advanced/03-template-specialization.md +++ /dev/null @@ -1,1982 +0,0 @@ ---- -chapter: 12 -cpp_standard: -- 11 -- 14 -- 17 -- 20 -description: In-depth understanding of C++ template specialization mechanisms and - matching rules -difficulty: intermediate -order: 3 -platform: host -prerequisites: -- 'Chapter 12: 模板入门概述' -- 'Chapter 12: 函数模板详解' -reading_time_minutes: 62 -tags: -- cpp-modern -- host -- intermediate -title: Template specialization and partial specialization ---- -# Modern C++ for Embedded Systems Tutorial — Template Specialization and Partial Specialization - -In previous chapters, we covered the basic syntax of templates and the deduction rules for function templates. However, a generic template definition is not always suitable for every type—you might need to provide a completely different implementation for a specific type, or customize behavior for a category of types. - -This is where **template specialization** comes into play. It is one of the most powerful tools in C++ template programming, and it forms the foundation for implementing type traits. - -In this chapter, we will dive deep into: - -- The essential differences between full specialization and partial specialization -- Matching rules for class template partial specialization -- Why function templates should use overloading instead of specialization -- Hands-on practice: implementing a complete traits class - ------- - -## Specialization Overview: Why Do We Need Specialization? - -### Limitations of Generic Templates - -Suppose we are writing a generic comparison utility: - -```cpp -template -bool equals(const T& a, const T& b) { - return a == b; -} -``` - -For most types, this works perfectly. But consider these scenarios: - -```cpp -// 场景1:C风格字符串 -equals("hello", "hello"); // 比较指针地址,而非字符串内容! - -// 场景2:浮点数 -equals(0.1 + 0.2, 0.3); // 浮点精度问题,可能返回false - -// 场景3:指针类型 -equals(&p1, &p2); // 比较指针值,可能不是你想要的 -``` - -This is where specialization shines—**providing a dedicated implementation for specific types**. - -### Categories of Specialization - -C++ template specialization falls into two main categories: - -| Specialization Type | Class Template | Function Template | -|---------------------|----------------|-------------------| -| Full Specialization (Explicit Specialization) | Supported | Supported (but not recommended) | -| Partial Specialization | Supported | Not supported | - -We will explore both types of specialization in detail next. - ------- - -## Full Specialization: Complete Customization - -### What Is Full Specialization? - -Full specialization provides a completely independent implementation for **specific template arguments**. Once specialized, all the code from the original template can be replaced. - -### Full Specialization of Class Templates - -```cpp -// 主模板(通用版本) -template -class TypeInfo { -public: - static const char* name() { return "unknown"; } - static constexpr size_t size = sizeof(T); -}; - -// 为int全特化 -template<> -class TypeInfo { -public: - static const char* name() { return "signed integer"; } - static constexpr size_t size = sizeof(int); - - // 特化版本可以添加额外的成员 - static constexpr bool is_signed = true; - static constexpr int min_value = INT_MIN; - static constexpr int max_value = INT_MAX; -}; - -// 为double全特化 -template<> -class TypeInfo { -public: - static const char* name() { return "double precision float"; } - static constexpr size_t size = sizeof(double); - static constexpr bool is_signed = true; -}; - -// 使用 -std::cout << TypeInfo::name(); // "unknown"(使用主模板) -std::cout << TypeInfo::name(); // "signed integer"(使用特化) -std::cout << TypeInfo::name(); // "double precision float" -``` - -**Syntax key points**: - -- `template<>` indicates that this is a specialization -- It is immediately followed by the class name and the concrete types: `class TypeInfo` -- The specialized version can completely redefine the contents of the class - -### Full Specialization of Function Templates - -```cpp -// 主模板 -template -bool is_negative(T value) { - return value < 0; -} - -// 为unsigned int全特化 -template<> -bool is_negative(unsigned int value) { - (void)value; // 避免unused参数警告 - return false; // 无符号数永远不为负 -} - -// 使用 -is_negative(-5); // 调用主模板,T=int -is_negative(5u); // 调用特化版本 -is_negative(3.14); // 调用主模板,T=double -``` - -### Embedded Application Scenario: Register Access Specialization - -```cpp -// 通用的寄存器访问模板 -template -class Register { -public: - static void write(T value) { - *reinterpret_cast(Address) = value; - } - - static T read() { - return *reinterpret_cast(Address); - } -}; - -// 为布尔寄存器(1位)全特化 -template -class Register { -public: - static void write(bool value) { - if (value) { - *reinterpret_cast(Address) = 1; - } else { - *reinterpret_cast(Address) = 0; - } - } - - static bool read() { - return *reinterpret_cast(Address) != 0; - } - - // 添加特有操作 - static void set() { - *reinterpret_cast(Address) = 1; - } - - static void clear() { - *reinterpret_cast(Address) = 0; - } - - static void toggle() { - auto reg = reinterpret_cast(Address); - *reg = !*reg; - } -}; - -// 使用 -Register gpio_mode; // 通用版本 -Register gpio_output; // 特化版本,有set/clear/toggle - -gpio_mode.write(0x44444444); -gpio_output.set(); -``` - ------- - -## Partial Specialization: Partial Customization - -### What Is Partial Specialization? - -Partial specialization specializes only **some template parameters**, or specializes based on a certain **attribute** of the parameters. - -**Key points**: - -- Only **class templates** support partial specialization -- Function templates **do not** support partial specialization - -### Partial Specialization Based on the Number of Template Parameters - -```cpp -// 主模板:两个类型参数 -template -class Pair { -public: - T first; - U second; - Pair() : first{}, second{} {} - Pair(const T& t, const U& u) : first(t), second(u) {} - - void print() const { - std::cout << "Pair: " << first << ", " << second << '\n'; - } -}; - -// 偏特化:两个类型相同时 -template -class Pair { -public: - T first; - T second; - Pair() : first{}, second{} {} - Pair(const T& t, const T& u) : first(t), second(u) {} - - // 可以添加针对相同类型的特殊操作 - T sum() const { return first + second; } - bool are_equal() const { return first == second; } - - void print() const { - std::cout << "Pair: " << first << ", " << second << '\n'; - } -}; - -// 偏特化:第二个参数是指针 -template -class Pair { -public: - T first; - U* second; - Pair() : first{}, second{nullptr} {} - Pair(const T& t, U* u) : first(t), second(u) {} - - // 针对指针的特殊处理 - void print() const { - std::cout << "Pair: " << first << ", " - << (second ? *second : U{}) << '\n'; - } -}; - -// 使用 -Pair p1; // 使用主模板 -Pair p2; // 使用偏特化 -Pair p3; // 使用偏特化 -``` - -### Partial Specialization Based on Type Attributes - -This is the most powerful use of partial specialization—targeting the **nature** of a type rather than the type itself: - -```cpp -// 主模板 -template -class TypeProperties { -public: - static constexpr bool is_pointer = false; - static constexpr bool is_const = false; - static constexpr bool is_reference = false; - using value_type = T; - using base_type = T; -}; - -// 指针类型的偏特化 -template -class TypeProperties { -public: - static constexpr bool is_pointer = true; - static constexpr bool is_const = false; - static constexpr bool is_reference = false; - using value_type = T; - using base_type = T; // 去掉指针后的类型 -}; - -// const指针的偏特化 -template -class TypeProperties { -public: - static constexpr bool is_pointer = true; - static constexpr bool is_const = true; - static constexpr bool is_reference = false; - using value_type = T; - using base_type = T; -}; - -// 引用类型的偏特化 -template -class TypeProperties { -public: - static constexpr bool is_pointer = false; - static constexpr bool is_const = false; - static constexpr bool is_reference = true; - using value_type = T; - using base_type = T; -}; - -// 使用 -TypeProperties::is_pointer; // false -TypeProperties::is_pointer; // true -TypeProperties::is_const; // true -TypeProperties::is_reference; // true -TypeProperties::base_type; // int -``` - -### Hierarchy of Partial Specializations - -Partial specializations can have multiple levels, and the compiler will select the **most specialized** version: - -```cpp -// 层次0:主模板 -template -class Container { -public: - static constexpr int level = 0; -}; - -// 层次1:指针偏特化 -template -class Container { -public: - static constexpr int level = 1; -}; - -// 层次2:const指针偏特化 -template -class Container { -public: - static constexpr int level = 2; -}; - -// 层次3:指向const的const指针 -template -class Container { -public: - static constexpr int level = 3; -}; - -// 使用 -Container::level; // 0(主模板) -Container::level; // 1(指针) -Container::level; // 2(const指针) -Container::level; // 3(const T* const) -``` - -### Partial Specialization Matching with Multiple Template Parameters - -When multiple partial specializations exist, the compiler selects based on the "most specialized" principle: - -```cpp -// 主模板 -template -class Test { -public: - static constexpr const char* desc = "primary"; -}; - -// 偏特化1:固定第三个参数 -template -class Test { -public: - static constexpr const char* desc = "N=10"; -}; - -// 偏特化2:前两个参数相同 -template -class Test { -public: - static constexpr const char* desc = "T=U"; -}; - -// 偏特化3:前两个参数相同且N=10 -template -class Test { -public: - static constexpr const char* desc = "T=U, N=10"; -}; - -// 使用 -Test::desc; // "primary"(主模板) -Test::desc; // "N=10"(偏特化1) -Test::desc; // "T=U"(偏特化2) -Test::desc; // "T=U, N=10"(偏特化3,最特化) -``` - -### How Does the Compiler Choose the "Most Specialized" Version? - -Partial specialization selection follows the **more specialized** rule: - -| Rule | Description | -|------|-------------| -| Some parameters are fixed | A version with some fixed parameters is more specialized than one with all generic parameters | -| More constraints | A version with more constraints is more specialized | -| More specific match | A version that can match more types is more generic | - -```cpp -// 示例:理解"更特化" -template -struct A { static constexpr int value = 0; }; // 通用 - -template -struct A { static constexpr int value = 1; }; // 更特化(指针) - -template -struct A { static constexpr int value = 2; }; // 最特化(const指针) - -A::value; // 0 -A::value; // 1 -A::value; // 2 -``` - ------- - -## Matching Rules Explained - -### Complete Matching Order - -When the compiler encounters a template usage, it matches in the following order: - -```text -1. 尝试完全匹配的全特化 -2. 尝试匹配所有偏特化,选择最特化的 -3. 如果没有特化匹配,使用主模板 -4. 如果都没有,编译错误 -``` - -### Matching Examples - -```cpp -// 主模板 -template -class Selector { -public: - static constexpr int choice = 0; -}; - -// 偏特化:T是指针 -template -class Selector { -public: - static constexpr int choice = 1; -}; - -// 偏特化:U是指针 -template -class Selector { -public: - static constexpr int choice = 2; -}; - -// 偏特化:两者都是指针 -template -class Selector { -public: - static constexpr int choice = 3; -}; - -// 测试 -static_assert(Selector::choice == 0); // 主模板 -static_assert(Selector::choice == 1); // T*偏特化 -static_assert(Selector::choice == 2); // U*偏特化 -static_assert(Selector::choice == 3); // 都是指针 -``` - -### Matching with References and cv-Qualifiers - -```cpp -// 主模板 -template -class Matcher { -public: - static constexpr int value = 0; -}; - -// const偏特化 -template -class Matcher { -public: - static constexpr int value = 1; -}; - -// 引用偏特化 -template -class Matcher { -public: - static constexpr int value = 2; -}; - -// rvalue引用偏特化 -template -class Matcher { -public: - static constexpr int value = 3; -}; - -// const引用偏特化 -template -class Matcher { -public: - static constexpr int value = 4; -}; - -// 测试 -static_assert(Matcher::value == 0); // 主模板 -static_assert(Matcher::value == 1); // const偏特化 -static_assert(Matcher::value == 2); // 引用偏特化 -static_assert(Matcher::value == 3); // rvalue引用 -static_assert(Matcher::value == 4); // const引用 -``` - -### Caveat: Decay and Matching - -Types consider decay during matching: - -```cpp -// 主模板 -template -class DecayTest { -public: - static constexpr int value = 0; - static constexpr const char* name = "primary"; -}; - -// 数组偏特化 -template -class DecayTest { -public: - static constexpr int value = 1; - static constexpr const char* name = "array"; -}; - -// 函数偏特化 -template -class DecayTest { -public: - static constexpr int value = 2; - static constexpr const char* name = "function"; -}; - -// 指针偏特化 -template -class DecayTest { -public: - static constexpr int value = 3; - static constexpr const char* name = "pointer"; -}; - -// 测试 -DecayTest::name; // "array" -DecayTest::name; // "function" -DecayTest::name; // "pointer" -``` - ------- - -## The Truth About Function Template Specialization - -### Why Do Function Templates "Not Support" Partial Specialization? - -To be precise: **C++ allows full specialization of function templates, but does not allow partial specialization**. When you want to "partially specialize" a function template, you should use **function overloading**. - -### Syntax of Function Template Full Specialization - -```cpp -// 主模板 -template -void process(T value) { - std::cout << "Generic: " << value << '\n'; -} - -// 全特化:为int -template<> -void process(int value) { - std::cout << "Int specialized: " << value << '\n'; -} - -// 全特化:可以省略,编译器会推导 -template<> -void process(double value) { - std::cout << "Double specialized: " << value << '\n'; -} - -// 使用 -process(42); // 输出 "Int specialized: 42" -process(3.14); // 输出 "Double specialized: 3.14" -process('a'); // 输出 "Generic: a" -``` - -### Why Shouldn't We Specialize Function Templates? - -This is a very common pitfall. Function template specialization has several serious issues: - -#### Issue 1: Specializations Do Not Participate in Overload Resolution - -```cpp -// 主模板 -template -void func(T t) { - std::cout << "Template\n"; -} - -// 特化版本 -template<> -void func(int t) { - std::cout << "Specialization\n"; -} - -// 普通重载 -void func(int t) { - std::cout << "Overload\n"; -} - -// 调用 -func(42); // 输出 "Overload" —— 普通函数优先! -``` - -**The specialized version has lower priority than a regular function overload**, which can lead to unexpected behavior. - -#### Issue 2: Cannot Be Partially Specialized - -```cpp -// ❌ 这不是合法的C++! -template -void process(T* ptr) { // 这看起来像偏特化,实际上是新的主模板 - std::cout << "Pointer\n"; -} - -// 这样做会创建一个新的主模板,导致二义性错误 -template -void process(T t) { - std::cout << "Generic\n"; -} - -process(42); // 错误!两个主模板都匹配 -``` - -### Solution: Use Function Overloading - -**Best practice**: Replace function template specialization with function overloading. - -```cpp -// ✅ 正确方式:使用重载 - -// 主模板 -template -void func(T t) { - std::cout << "Generic: " << t << '\n'; -} - -// 重载:指针版本 -template -void func(T* ptr) { - std::cout << "Pointer: " << ptr << '\n'; -} - -// 重载:const char*版本 -void func(const char* str) { - std::cout << "C-string: " << str << '\n'; -} - -// 使用 -func(42); // "Generic: 42" -int x = 10; -func(&x); // "Pointer: <地址>" -func("hello"); // "C-string: hello" -``` - -### Decision Table: Specialization vs. Overloading - -| Scenario | Class Template | Function Template | -|----------|----------------|-------------------| -| Need complete customization | Use full specialization | Use overloading | -| Need partial customization | Use partial specialization | Use overloading | -| Need to participate in overload resolution | Not applicable | Use overloading | -| Need to modify class members | Use specialization | Not applicable | - -### When Should We Use Function Template Specialization? - -Function template specialization should only be used in these limited scenarios: - -1. **Modifying a member function of a class template**: - - ```cpp - template - class MyClass { - void func(); // 成员函数模板 - }; - - // 特化整个类的成员函数 - template<> - void MyClass::func() { - // int版本的实现 - } - ``` - -2. **Working in conjunction with class template specialization**: - - ```cpp - template - class MyClass { - template - void helper(U u); - }; - - // 只能通过特化MyClass来特化helper - ``` - -Aside from these cases, **prefer function overloading**. - ------- - -## Hands-on Practice: Implementing a Complete Traits Class - -Type traits are a core tool in C++ template metaprogramming. Let us implement a complete traits class from scratch, including specializations for pointer types. - -### Requirements Analysis - -Our traits class needs to provide: - -1. **Type information**: underlying type, value type, types with references/const removed -2. **Type attributes**: whether it is a pointer, reference, const, or arithmetic type -3. **Type transformations**: adding/removing pointers, references, and const -4. **Type relationships**: whether two types are the same, whether one is convertible to another - -### Basic Framework - -```cpp -// 基础traits定义 -template -class TypeTraits { -public: - // 类型信息 - using BaseType = T; // 去掉所有修饰符后的类型 - using ValueType = T; // 容器存储的值类型 - using ReferenceType = T&; // 对应的引用类型 - using PointerType = T*; // 对应的指针类型 - - // 类型属性 - static constexpr bool is_pointer = false; - static constexpr bool is_reference = false; - static constexpr bool is_const = false; - static constexpr bool is_volatile = false; - static constexpr bool is_void = false; - static constexpr bool is_integral = false; - static constexpr bool is_floating_point = false; - static constexpr bool is_arithmetic = false; - static constexpr bool is_signed = false; - static constexpr bool is_unsigned = false; - - // 便捷常量 - static constexpr bool is_primitive = is_arithmetic || is_void; -}; -``` - -### Pointer Type Specialization - -```cpp -// 指针类型的偏特化 -template -class TypeTraits { -public: - using BaseType = typename TypeTraits::BaseType; // 递归获取 - using ValueType = T; // 指针指向的类型 - using ReferenceType = T&; - using PointerType = T*; - - static constexpr bool is_pointer = true; - static constexpr bool is_reference = false; - static constexpr bool is_const = TypeTraits::is_const; - static constexpr bool is_volatile = TypeTraits::is_volatile; - static constexpr bool is_void = TypeTraits::is_void; - static constexpr bool is_integral = TypeTraits::is_integral; - static constexpr bool is_floating_point = TypeTraits::is_floating_point; - static constexpr bool is_arithmetic = TypeTraits::is_arithmetic; - static constexpr bool is_signed = TypeTraits::is_signed; - static constexpr bool is_unsigned = TypeTraits::is_unsigned; - - static constexpr bool is_primitive = is_arithmetic || is_void; -}; - -// const指针的偏特化 -template -class TypeTraits { -public: - using BaseType = typename TypeTraits::BaseType; - using ValueType = const T; - using ReferenceType = const T&; - using PointerType = const T*; - - static constexpr bool is_pointer = true; - static constexpr bool is_reference = false; - static constexpr bool is_const = true; // 这是const指针 - static constexpr bool is_volatile = TypeTraits::is_volatile; - static constexpr bool is_void = TypeTraits::is_void; - static constexpr bool is_integral = TypeTraits::is_integral; - static constexpr bool is_floating_point = TypeTraits::is_floating_point; - static constexpr bool is_arithmetic = TypeTraits::is_arithmetic; - static constexpr bool is_signed = TypeTraits::is_signed; - static constexpr bool is_unsigned = TypeTraits::is_unsigned; - - static constexpr bool is_primitive = is_arithmetic || is_void; -}; -``` - -### Reference Type Specialization - -```cpp -// 左值引用的偏特化 -template -class TypeTraits { -public: - using BaseType = typename TypeTraits::BaseType; - using ValueType = T; - using ReferenceType = T&; - using PointerType = T*; - - static constexpr bool is_pointer = false; - static constexpr bool is_reference = true; - static constexpr bool is_const = TypeTraits::is_const; - static constexpr bool is_volatile = TypeTraits::is_volatile; - static constexpr bool is_void = TypeTraits::is_void; - static constexpr bool is_integral = TypeTraits::is_integral; - static constexpr bool is_floating_point = TypeTraits::is_floating_point; - static constexpr bool is_arithmetic = TypeTraits::is_arithmetic; - static constexpr bool is_signed = TypeTraits::is_signed; - static constexpr bool is_unsigned = TypeTraits::is_unsigned; - - static constexpr bool is_primitive = is_arithmetic || is_void; -}; - -// 右值引用的偏特化 (C++11) -template -class TypeTraits { -public: - using BaseType = typename TypeTraits::BaseType; - using ValueType = T; - using ReferenceType = T&&; - using PointerType = T*; - - static constexpr bool is_pointer = false; - static constexpr bool is_reference = true; // rvalue也是引用 - static constexpr bool is_const = TypeTraits::is_const; - static constexpr bool is_volatile = TypeTraits::is_volatile; - static constexpr bool is_void = TypeTraits::is_void; - static constexpr bool is_integral = TypeTraits::is_integral; - static constexpr bool is_floating_point = TypeTraits::is_floating_point; - static constexpr bool is_arithmetic = TypeTraits::is_arithmetic; - static constexpr bool is_signed = TypeTraits::is_signed; - static constexpr bool is_unsigned = TypeTraits::is_unsigned; - - static constexpr bool is_primitive = is_arithmetic || is_void; -}; -``` - -### Full Specialization for Fundamental Types - -```cpp -// void的特化 -template<> -class TypeTraits { -public: - using BaseType = void; - using ValueType = void; - using ReferenceType = void; - using PointerType = void*; - - static constexpr bool is_pointer = false; - static constexpr bool is_reference = false; - static constexpr bool is_const = false; - static constexpr bool is_volatile = false; - static constexpr bool is_void = true; - static constexpr bool is_integral = false; - static constexpr bool is_floating_point = false; - static constexpr bool is_arithmetic = false; - static constexpr bool is_signed = false; - static constexpr bool is_unsigned = false; - - static constexpr bool is_primitive = true; -}; - -// 整数类型的宏辅助 -#define INTEGRAL_TRAITS(Type, IsSigned) \ -template<> \ -class TypeTraits { \ -public: \ - using BaseType = Type; \ - using ValueType = Type; \ - using ReferenceType = Type&; \ - using PointerType = Type*; \ -\ - static constexpr bool is_pointer = false; \ - static constexpr bool is_reference = false; \ - static constexpr bool is_const = false; \ - static constexpr bool is_volatile = false; \ - static constexpr bool is_void = false; \ - static constexpr bool is_integral = true; \ - static constexpr bool is_floating_point = false; \ - static constexpr bool is_arithmetic = true; \ - static constexpr bool is_signed = IsSigned; \ - static constexpr bool is_unsigned = !IsSigned; \ -\ - static constexpr bool is_primitive = true; \ -} - -// 为所有整数类型定义特化 -INTEGRAL_TRAITS(char, true); -INTEGRAL_TRAITS(signed char, true); -INTEGRAL_TRAITS(unsigned char, false); -INTEGRAL_TRAITS(short, true); -INTEGRAL_TRAITS(unsigned short, false); -INTEGRAL_TRAITS(int, true); -INTEGRAL_TRAITS(unsigned int, false); -INTEGRAL_TRAITS(long, true); -INTEGRAL_TRAITS(unsigned long, false); -INTEGRAL_TRAITS(long long, true); -INTEGRAL_TRAITS(unsigned long long, false); - -// 浮点类型的宏辅助 -#define FLOATING_TRAITS(Type) \ -template<> \ -class TypeTraits { \ -public: \ - using BaseType = Type; \ - using ValueType = Type; \ - using ReferenceType = Type&; \ - using PointerType = Type*; \ -\ - static constexpr bool is_pointer = false; \ - static constexpr bool is_reference = false; \ - static constexpr bool is_const = false; \ - static constexpr bool is_volatile = false; \ - static constexpr bool is_void = false; \ - static constexpr bool is_integral = false; \ - static constexpr bool is_floating_point = true; \ - static constexpr bool is_arithmetic = true; \ - static constexpr bool is_signed = true; \ - static constexpr bool is_unsigned = false; \ -\ - static constexpr bool is_primitive = true; \ -} - -FLOATING_TRAITS(float); -FLOATING_TRAITS(double); -FLOATING_TRAITS(long double); -``` - -### Helper Alias Templates - -```cpp -// 便捷的类型别名 -template -using BaseType_t = typename TypeTraits::BaseType; - -template -using ValueType_t = typename TypeTraits::ValueType; - -template -using AddPointer_t = typename TypeTraits::PointerType; - -template -using AddReference_t = typename TypeTraits::ReferenceType; - -// 检查类型属性的常量 -template -inline constexpr bool is_pointer_v = TypeTraits::is_pointer; - -template -inline constexpr bool is_reference_v = TypeTraits::is_reference; - -template -inline constexpr bool is_arithmetic_v = TypeTraits::is_arithmetic; -``` - -### Usage Examples - -```cpp -// 测试代码 -void test_type_traits() { - // 指针类型 - static_assert(is_pointer_v == true); - static_assert(is_pointer_v == false); - static_assert(is_pointer_v == true); - - // 引用类型 - static_assert(is_reference_v == true); - static_assert(is_reference_v == false); - static_assert(is_reference_v == true); - - // 算术类型 - static_assert(is_arithmetic_v == true); - static_assert(is_arithmetic_v == true); - static_assert(is_arithmetic_v == false); - - // 嵌套类型 - static_assert(std::is_same_v, int>); - static_assert(std::is_same_v, int>); - static_assert(std::is_same_v, int>); -} -``` - -### Embedded Application: Smart Pointer Traits - -```cpp -// 用于嵌入式智能指针的traits -template -class SmartPtrTraits { -public: - using pointer_type = T*; - using element_type = T; - using deleter_type = void(*)(T*); - - static void default_delete(T* ptr) { - delete ptr; - } - - static constexpr bool is_array = false; - static constexpr bool is_shared = false; -}; - -// 数组类型的偏特化 -template -class SmartPtrTraits { -public: - using pointer_type = T*; - using element_type = T; - using deleter_type = void(*)(T*); - - static void default_delete(T* ptr) { - delete[] ptr; - } - - static constexpr bool is_array = true; - static constexpr bool is_shared = false; -}; - -// 使用traits的智能指针基类 -template> -class SmartPtrBase { -protected: - using Ptr = typename Traits::pointer_type; - Ptr ptr_; - - void cleanup() { - if (ptr_) { - Traits::default_delete(ptr_); - } - } - -public: - constexpr SmartPtrBase(std::nullptr_t = nullptr) noexcept : ptr_(nullptr) {} - - explicit SmartPtrBase(Ptr p) noexcept : ptr_(p) {} - - ~SmartPtrBase() { - cleanup(); - } - - // 禁用拷贝 - SmartPtrBase(const SmartPtrBase&) = delete; - SmartPtrBase& operator=(const SmartPtrBase&) = delete; - - // 支持移动 - SmartPtrBase(SmartPtrBase&& other) noexcept : ptr_(other.ptr_) { - other.ptr_ = nullptr; - } - - SmartPtrBase& operator=(SmartPtrBase&& other) noexcept { - if (this != &other) { - cleanup(); - ptr_ = other.ptr_; - other.ptr_ = nullptr; - } - return *this; - } - - // 访问器 - Ptr get() const noexcept { return ptr_; } - explicit operator bool() const noexcept { return ptr_ != nullptr; } - - // 特化操作 - void reset(Ptr p = nullptr) noexcept { - cleanup(); - ptr_ = p; - } - - Ptr release() noexcept { - Ptr tmp = ptr_; - ptr_ = nullptr; - return tmp; - } -}; - -// 具体的智能指针实现 -template -class UniquePtr : public SmartPtrBase> { - using Base = SmartPtrBase>; - -public: - using Base::Base; - - // 解引用操作符 - T& operator*() const { return *this->get(); } - T* operator->() const { return this->get(); } -}; - -// 数组特化版本 -template -class UniquePtr : public SmartPtrBase> { - using Base = SmartPtrBase>; - -public: - using Base::Base; - - // 数组下标操作 - T& operator[](size_t index) const { return this->get()[index]; } -}; - -// 使用 -UniquePtr ptr1(new int(42)); -UniquePtr arr(new int[10]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); -``` - -::: details Complete TypeTraits Implementation (Expandable) - -```cpp -#ifndef TYPE_TRAITS_HPP -#define TYPE_TRAITS_HPP - -#include -#include - -template -class TypeTraits { -public: - using BaseType = T; - using ValueType = T; - using ReferenceType = T&; - using PointerType = T*; - - static constexpr bool is_pointer = false; - static constexpr bool is_reference = false; - static constexpr bool is_const = false; - static constexpr bool is_volatile = false; - static constexpr bool is_void = false; - static constexpr bool is_integral = false; - static constexpr bool is_floating_point = false; - static constexpr bool is_arithmetic = false; - static constexpr bool is_signed = false; - static constexpr bool is_unsigned = false; - static constexpr bool is_primitive = is_arithmetic || is_void; -}; - -// 指针偏特化 -template -class TypeTraits { -public: - using BaseType = typename TypeTraits::BaseType; - using ValueType = T; - using ReferenceType = T&; - using PointerType = T*; - - static constexpr bool is_pointer = true; - static constexpr bool is_const = TypeTraits::is_const; - static constexpr bool is_volatile = TypeTraits::is_volatile; - static constexpr bool is_void = TypeTraits::is_void; - static constexpr bool is_integral = TypeTraits::is_integral; - static constexpr bool is_floating_point = TypeTraits::is_floating_point; - static constexpr bool is_arithmetic = TypeTraits::is_arithmetic; - static constexpr bool is_signed = TypeTraits::is_signed; - static constexpr bool is_unsigned = TypeTraits::is_unsigned; - static constexpr bool is_primitive = is_arithmetic || is_void; -}; - -// const指针偏特化 -template -class TypeTraits { -public: - using BaseType = typename TypeTraits::BaseType; - using ValueType = const T; - using ReferenceType = const T&; - using PointerType = const T*; - - static constexpr bool is_pointer = true; - static constexpr bool is_const = true; - static constexpr bool is_volatile = TypeTraits::is_volatile; - static constexpr bool is_void = TypeTraits::is_void; - static constexpr bool is_integral = TypeTraits::is_integral; - static constexpr bool is_floating_point = TypeTraits::is_floating_point; - static constexpr bool is_arithmetic = TypeTraits::is_arithmetic; - static constexpr bool is_signed = TypeTraits::is_signed; - static constexpr bool is_unsigned = TypeTraits::is_unsigned; - static constexpr bool is_primitive = is_arithmetic || is_void; -}; - -// 引用偏特化 -template -class TypeTraits { -public: - using BaseType = typename TypeTraits::BaseType; - using ValueType = T; - using ReferenceType = T&; - using PointerType = T*; - - static constexpr bool is_reference = true; - static constexpr bool is_const = TypeTraits::is_const; - static constexpr bool is_volatile = TypeTraits::is_volatile; - static constexpr bool is_void = TypeTraits::is_void; - static constexpr bool is_integral = TypeTraits::is_integral; - static constexpr bool is_floating_point = TypeTraits::is_floating_point; - static constexpr bool is_arithmetic = TypeTraits::is_arithmetic; - static constexpr bool is_signed = TypeTraits::is_signed; - static constexpr bool is_unsigned = TypeTraits::is_unsigned; - static constexpr bool is_primitive = is_arithmetic || is_void; -}; - -// rvalue引用偏特化 -template -class TypeTraits { -public: - using BaseType = typename TypeTraits::BaseType; - using ValueType = T; - using ReferenceType = T&&; - using PointerType = T*; - - static constexpr bool is_reference = true; - static constexpr bool is_const = TypeTraits::is_const; - static constexpr bool is_volatile = TypeTraits::is_volatile; - static constexpr bool is_void = TypeTraits::is_void; - static constexpr bool is_integral = TypeTraits::is_integral; - static constexpr bool is_floating_point = TypeTraits::is_floating_point; - static constexpr bool is_arithmetic = TypeTraits::is_arithmetic; - static constexpr bool is_signed = TypeTraits::is_signed; - static constexpr bool is_unsigned = TypeTraits::is_unsigned; - static constexpr bool is_primitive = is_arithmetic || is_void; -}; - -// void特化 -template<> -class TypeTraits { -public: - using BaseType = void; - using ValueType = void; - using ReferenceType = void; - using PointerType = void*; - - static constexpr bool is_pointer = false; - static constexpr bool is_reference = false; - static constexpr bool is_const = false; - static constexpr bool is_volatile = false; - static constexpr bool is_void = true; - static constexpr bool is_integral = false; - static constexpr bool is_floating_point = false; - static constexpr bool is_arithmetic = false; - static constexpr bool is_signed = false; - static constexpr bool is_unsigned = false; - static constexpr bool is_primitive = true; -}; - -// 整数类型宏 -#define INTEGRAL_TRAITS(Type, IsSigned) \ -template<> \ -class TypeTraits { \ -public: \ - using BaseType = Type; \ - using ValueType = Type; \ - using ReferenceType = Type&; \ - using PointerType = Type*; \ -\ - static constexpr bool is_pointer = false; \ - static constexpr bool is_reference = false; \ - static constexpr bool is_const = false; \ - static constexpr bool is_volatile = false; \ - static constexpr bool is_void = false; \ - static constexpr bool is_integral = true; \ - static constexpr bool is_floating_point = false; \ - static constexpr bool is_arithmetic = true; \ - static constexpr bool is_signed = IsSigned; \ - static constexpr bool is_unsigned = !IsSigned; \ - static constexpr bool is_primitive = true; \ -} - -INTEGRAL_TRAITS(char, true); -INTEGRAL_TRAITS(signed char, true); -INTEGRAL_TRAITS(unsigned char, false); -INTEGRAL_TRAITS(short, true); -INTEGRAL_TRAITS(unsigned short, false); -INTEGRAL_TRAITS(int, true); -INTEGRAL_TRAITS(unsigned int, false); -INTEGRAL_TRAITS(long, true); -INTEGRAL_TRAITS(unsigned long, false); -INTEGRAL_TRAITS(long long, true); -INTEGRAL_TRAITS(unsigned long long, false); -INTEGRAL_TRAITS(int8_t, true); -INTEGRAL_TRAITS(int16_t, true); -INTEGRAL_TRAITS(int32_t, true); -INTEGRAL_TRAITS(int64_t, true); -INTEGRAL_TRAITS(uint8_t, false); -INTEGRAL_TRAITS(uint16_t, false); -INTEGRAL_TRAITS(uint32_t, false); -INTEGRAL_TRAITS(uint64_t, false); - -// 浮点类型宏 -#define FLOATING_TRAITS(Type) \ -template<> \ -class TypeTraits { \ -public: \ - using BaseType = Type; \ - using ValueType = Type; \ - using ReferenceType = Type&; \ - using PointerType = Type*; \ -\ - static constexpr bool is_pointer = false; \ - static constexpr bool is_reference = false; \ - static constexpr bool is_const = false; \ - static constexpr bool is_volatile = false; \ - static constexpr bool is_void = false; \ - static constexpr bool is_integral = false; \ - static constexpr bool is_floating_point = true; \ - static constexpr bool is_arithmetic = true; \ - static constexpr bool is_signed = true; \ - static constexpr bool is_unsigned = false; \ - static constexpr bool is_primitive = true; \ -} - -FLOATING_TRAITS(float); -FLOATING_TRAITS(double); -FLOATING_TRAITS(long double); - -// 便捷别名和变量模板 -template -using BaseType_t = typename TypeTraits::BaseType; - -template -using ValueType_t = typename TypeTraits::ValueType; - -template -using AddPointer_t = typename TypeTraits::PointerType; - -template -using AddReference_t = typename TypeTraits::ReferenceType; - -template -inline constexpr bool is_pointer_v = TypeTraits::is_pointer; - -template -inline constexpr bool is_reference_v = TypeTraits::is_reference; - -template -inline constexpr bool is_arithmetic_v = TypeTraits::is_arithmetic; - -template -inline constexpr bool is_integral_v = TypeTraits::is_integral; - -template -inline constexpr bool is_floating_point_v = TypeTraits::is_floating_point; - -#endif // TYPE_TRAITS_HPP - - ``` - -::: - ------- - -## Tracing the Standard Library: std::iterator_traits - -### Design Background - -In the C++ standard library, `std::iterator_traits` is a classic application case for specialization. It allows algorithms to handle different types of iterators in a uniform way. - -### Structure of iterator_traits - -``` - -// C++标准库中的简化版本 -namespace std { - -template -class iterator_traits { -public: - using iterator_category = typename Iterator::iterator_category; - using value_type = typename Iterator::value_type; - using difference_type = typename Iterator::difference_type; - using pointer = typename Iterator::pointer; - using reference = typename Iterator::reference; -}; - -// 指针类型的偏特化! -template -class iterator_traits { -public: - using iterator_category = random_access_iterator_tag; - using value_type = T; - using difference_type = ptrdiff_t; - using pointer = T*; - using reference = T&; -}; - -// const指针的偏特化 -template -class iterator_traits { -public: - using iterator_category = random_access_iterator_tag; - using value_type = T; // 注意:是T而非const T - using difference_type = ptrdiff_t; - using pointer = const T*; - using reference = const T&; -}; - -} // namespace std - -```cpp - -### Why Do We Need Pointer Specialization? - -Consider a custom iterator: - -```cpp - -template -class MyIterator { -public: - using iterator_category = std::forward_iterator_tag; - using value_type = T; - using difference_type = std::ptrdiff_t; - using pointer = T*; - using reference = T&; - - // ... 迭代器实现 -}; - -```cpp - -Using `iterator_traits`: - -```cpp - -template -void process(Iter begin, Iter end) { - // 通过traits获取类型 - using traits = std::iterator_traits; - using value_type = typename traits::value_type; - - for (auto it = begin; it != end; ++it) { - value_type temp = *it; // 知道如何存储值 - // 处理... - } -} - -// 对于自定义迭代器 -MyIterator it1, it2; -process(it1, it2); // 使用主模板,从迭代器类型中提取 - -// 对于原生指针 -int arr[10]; -process(arr, arr + 10); // 使用指针偏特化! - -```cpp - -### Embedded Application: Ring Buffer Iterator - -```cpp - -#include - -template -class RingBuffer { -public: - // 迭代器定义 - class iterator { - public: - // 标准迭代器类型定义 - using iterator_category = std::random_access_iterator_tag; - using value_type = T; - using difference_type = std::ptrdiff_t; - using pointer = T*; - using reference = T&; - - iterator(RingBuffer* buf, size_t pos) - : buffer_(buf), pos_(pos) {} - - // 解引用 - reference operator*() const { - return buffer_->data_[pos_ % Size]; - } - - pointer operator->() const { - return &buffer_->data_[pos_ % Size]; - } - - // 算术运算 - iterator& operator++() { - ++pos_; - return *this; - } - - iterator operator++(int) { - iterator tmp = *this; - ++pos_; - return tmp; - } - - iterator& operator--() { - --pos_; - return *this; - } - - iterator operator--(int) { - iterator tmp = *this; - --pos_; - return tmp; - } - - iterator operator+(difference_type n) const { - return iterator(buffer_, pos_ + n); - } - - difference_type operator-(const iterator& other) const { - return pos_ - other.pos_; - } - - // 比较 - bool operator==(const iterator& other) const { - return buffer_ == other.buffer_ && pos_ == other.pos_; - } - - bool operator!=(const iterator& other) const { - return !(*this == other); - } - - private: - RingBuffer* buffer_; - size_t pos_; - }; - - // 构造函数 - RingBuffer() : head_(0), tail_(0), full_(false) {} - - // 元素访问 - bool push(const T& item) { - if (full_) { - return false; // 缓冲区满 - } - data_[tail_] = item; - tail_ = (tail_ + 1) % Size; - full_ = (head_ == tail_); - return true; - } - - bool pop(T& item) { - if (empty()) { - return false; // 缓冲区空 - } - item = data_[head_]; - head_ = (head_ + 1) % Size; - full_ = false; - return true; - } - - // 迭代器接口 - iterator begin() { return iterator(this, head_); } - iterator end() { - return iterator(this, full_ ? tail_ + Size : tail_); - } - - bool empty() const { - return !full_ && (head_ == tail_); - } - - bool full() const { - return full_; - } - -private: - T data_[Size]; - size_t head_; // 读位置 - size_t tail_; // 写位置 - bool full_; -}; - -// 使用示例 -void test_ring_buffer() { - RingBuffer buffer; - - // 添加元素 - for (int i = 0; i < 5; ++i) { - buffer.push(i); - } - - // 使用迭代器遍历 - auto it = buffer.begin(); - auto end = buffer.end(); - - // 使用iterator_traits获取类型 - using traits = std::iterator_traits; - using value_type = typename traits::value_type; - static_assert(std::is_same_v); - - // 算法兼容:可以使用STL算法 - value_type sum = 0; - for (; it != end; ++it) { - sum += *it; - } - - // 或使用标准算法 - RingBuffer buffer2; - for (int i = 0; i < 5; ++i) buffer2.push(i * 2); - - // std::copy兼容! - std::copy(buffer2.begin(), buffer2.end(), - std::ostream_iterator(std::cout, " ")); -} - -```cpp - -### Key Points for a Complete iterator_traits Implementation - -When implementing your own iterator_traits, note the following: - -| Key Point | Description | -|-----------|-------------| -| Default member types | iterator_category, value_type, difference_type, pointer, reference | -| Pointer specialization | Must provide partial specializations for T* and const T* | -| value_type selection | For const T*, value_type should be T (const removed) | -| iterator_category | Pointers use random_access_iterator_tag | - -### Modern C++ Improvements (C++20) - -C++20 introduced more concise aliases like `std::iter_reference_t`: - -```cpp - -// C++20风格 -template -using iter_value_t = typename std::iterator_traits::value_type; - -template -using iter_reference_t = typename std::iterator_traits::reference; - -// 使用 -template -void algorithm(Iter begin, Iter end) { - std::iter_value_t sum{}; // 更简洁的语法 -} - -``` - ------- - -## Common Pitfalls and Solutions - -### Pitfall 1: Infinite Recursion Caused by Mismatched Specialization - -```cpp - -// ❌ 错误:无限递归 -template -struct Traits { - using type = typename Traits::type; // 永远不会到达基础情况 -}; - -// ✅ 正确:提供基础情况 -template -struct Traits { - using type = T; -}; - -template -struct Traits { - using type = typename Traits::type; // 递归但会终止 -}; - -``` - -### Pitfall 2: Dependence on Specialization Order - -```cpp - -// ❌ 不要依赖特化的定义顺序 -template -struct A; - -template -struct A { - using type = T; -}; - -template -struct A { - using type = T; -}; - -// ✅ 主模板总是先定义 -template -struct A { - using type = T; -}; - -template -struct A { - using type = typename A::type; // 可以引用主模板 -}; - -``` - -### Pitfall 3: Correct Handling of const and References - -```cpp - -// ❌ 错误:const T& 不能正确匹配 -template -struct RemoveConst { - using type = T; -}; - -template -struct RemoveConst { - using type = T; -}; - -RemoveConst::type; // 还是 const int&,没去掉const! - -// ✅ 正确:先去掉引用,再处理const -template -struct RemoveConstImpl { - using type = T; -}; - -template -struct RemoveConstImpl { - using type = T; -}; - -template -struct RemoveConst { - using type = typename RemoveConstImpl>::type; -}; - -RemoveConst::type; // int,正确! - -``` - -### Pitfall 4: Confusing Function Template Specialization with Overloading - -```cpp - -// ❌ 错误:想特化但实际上定义了新模板 -template -void f(T*); - -template -void f(T); // 二义性! - -f(42); // 哪个f? - -// ✅ 正确:使用重载 -template -void f(T); - -template -void f(T*); // 重载,不是特化 - -``` - -### Pitfall 5: Forgetting the typename Keyword - -```cpp - -template -struct Traits { - using value_type = T; -}; - -template -void func() { - // ❌ 错误:编译器不知道 Traits::value_type 是类型 - Traits::value_type x = 10; - - // ✅ 正确:使用 typename - typename Traits::value_type x = 10; -} - -``` - -**Rule**: When accessing dependent types in a template, you must add the `typename` keyword. - -### Pitfall 6: Mixing Partial Specialization with SFINAE - -```cpp - -// ❌ 可能有问题:enable_if作为偏特化参数 -template>> -struct OnlyIntegral { - static constexpr bool value = true; -}; - -// 编译错误,而不是替换失败 -OnlyIntegral::value; - -// ✅ 使用 SFINAE 或 Concepts (C++20) -template -struct OnlyIntegral { - static constexpr bool value = false; -}; - -template -struct OnlyIntegral>> { - static constexpr bool value = true; -}; - -// 或 C++20 -template -concept Integral = std::is_integral_v; - -template -struct OnlyIntegral { - static constexpr bool value = true; -}; - -``` - ------- - -## C++20 New Feature: Concepts and Specialization - -### Concepts Simplify Constraints - -C++20 concepts can replace some specialization needs: - -```cpp - -// 传统方式:使用SFINAE和特化 -template -struct SupportsIncrement { - static constexpr bool value = false; -}; - -template -struct SupportsIncrement())>> { - static constexpr bool value = true; -}; - -// C++20:使用concept -template -concept SupportsIncrement = requires(T t) { - { ++t } -> std::same_as; -}; - -// 使用 -template -void increment_all(T*begin, T* end) { - for (auto it = begin; it != end; ++it) { - ++(*it); - } -} - -``` - -### requires Clauses Replacing Specialization - -```cpp - -// 传统:偏特化 -template -class SmartPtr { - // 通用实现 -}; - -template -class SmartPtr { - // 数组特化 -}; - -// C++20:使用 requires -template -class SmartPtr { - // 通用实现 -}; - -template -requires std::is_array_v -class SmartPtr { - // 数组版本(实际上是偏特化的语法糖) -}; - -``` - -### Concept Overloading Replacing Function Specialization - -```cpp - -// 传统:函数重载 + SFINAE -template -std::enable_if_t, T> -process(T value) { - return value + 1; -} - -template -std::enable_if_t, T> -process(T value) { - return value * 2.0; -} - -// C++20:concept 重载 -template -T process(T value) { - return value + 1; -} - -template -T process(T value) { - return value * 2.0; -} - -``` - ------- - -## Summary - -Template specialization is an advanced technique in C++ generic programming. Mastering it makes your code more flexible and efficient. - -### Summary of Key Points - -| Topic | Core Points | -|-------|-------------| -| Full specialization | Provides a completely independent implementation for specific template arguments, using the `template<>` syntax | -| Partial specialization | Only supported for class templates; can partially specialize or specialize based on type attributes | -| Matching rules | The compiler selects the "most specialized" version, ranked by the degree to which parameters are fixed | -| Function template specialization | Technically supports full specialization, but prefer overloading over specialization | -| Type traits | A classic application of specialization, used to obtain type information at compile time | - -### Practical Advice - -1. **Prefer partial specialization over full specialization**: Partial specialization can cover a category of types rather than a single type -2. **Use specialization for class templates**: Providing specializations for class templates is the standard approach -3. **Use overloading for function templates**: Function specialization has many limitations; overloading is more flexible -4. **Leverage the standard library**: `` already provides a rich set of traits; use them first -5. **Watch out for recursion termination**: Ensure there is a base case when using recursive specialization -6. **Use typename**: Do not forget typename when accessing dependent types in a template - -### Embedded Best Practices - -| Scenario | Recommended Approach | -|----------|----------------------| -| Peripheral type encapsulation | Specialize for pointer types to provide register-level access | -| Buffer implementation | Specialize for pointers/arrays to use zero-copy optimization | -| Serialization | Use traits to distinguish between POD and non-POD types | -| Interrupt handling | Specialize to provide interrupt-safe atomic operations | -| Memory management | Specialize for fixed-size types to avoid dynamic allocation | - -**In the next chapter**, we will explore **variadic templates**, learning how to write templates that accept an arbitrary number of arguments to implement type-safe formatting functions and compile-time algorithms. This is one of the core techniques of modern C++ metaprogramming. diff --git a/documents/en/vol4-advanced/04-if-constexpr.md b/documents/en/vol4-advanced/04-if-constexpr.md deleted file mode 100644 index 1f15a6765..000000000 --- a/documents/en/vol4-advanced/04-if-constexpr.md +++ /dev/null @@ -1,127 +0,0 @@ ---- -chapter: 4 -cpp_standard: -- 17 -- 20 -description: Detailed Explanation of if constexpr Applications -difficulty: intermediate -order: 4 -platform: host -prerequisites: -- 'Chapter 2: 零开支抽象' -reading_time_minutes: 4 -tags: -- cpp-modern -- host -- intermediate -title: compile-time condition with if constexpr ---- -# `if constexpr`: Making Compile-Time Branches Read Like Comments — A Practical Engineering Guide - -I have always believed that between modern C++ and classical C++98, most template programming techniques are written to combine for specific purposes, and this complexity is not always what we want. For example, in the template programming we will study later, many template-dependent `enable_if`, specializations, and SFINAE tricks are essentially just to achieve specific compile-time matching. Fortunately, we now have `if constexpr` to simplify the vast majority of these scenarios. - -`if constexpr` is a small yet powerful tool brought by C++17: it hands "branches that we can decide at compile time" directly to the compiler. The result is cleaner code and more readable templates, both of which can be simplified into a crisp `if constexpr`. - ------- - -## Why Should We Care - -- **Reduce the number of template specializations/overloads**: We can place behavior for different types within the same template body, forking via compile-time conditions. This centralizes the logic and makes maintenance easier. -- **Improve readability**: When reading code, seeing `if constexpr` immediately tells us this is a "type/constant-driven" branch, eliminating the need to hunt down other specialization implementations. -- **Avoid unnecessary instantiation errors**: Discarded branches are not instantiated, so expressions that are invalid for a certain type will not cause compilation failures. -- **Clear performance**: The compiler can eliminate unneeded branches at compile time, and the generated code is just as efficient as if we had written multiple specializations. - ------- - -## How to Use `if constexpr (cond)`? - -1. `if constexpr (cond)` requires `cond` to be determinable at compile time (a constant expression). -2. If `cond` is `true`, the compiler only compiles the `then` branch. The `else` (if present) is discarded and does not participate in template instantiation (therefore, it may contain code that is illegal for the current type). -3. The reverse applies as well. -4. If `cond` is not a compile-time constant, an error will occur (because the semantics of `if constexpr` require compile-time evaluation). (PS: It can now sometimes gradually degrade to runtime evaluation; this is a newer feature, so behavior varies slightly across different versions.) - ------- - -## How Does It Differ from a Regular `if`? - -A regular `if` is a runtime check, and both branches must be valid. `if constexpr` is a compile-time check, and only the selected branch needs to be valid (this is especially important for template-dependent conditions). - ------- - -### Case: Selecting an Implementation Based on Type - -Scenario: Printing values of different types (commonly used in engineering for log formatting, serialization branching, etc.). - -```cpp -#include -#include - -template -void printValue(const T& v) { - if constexpr (std::is_integral_v) { - std::cout << "整型: " << v << "\n"; - } else if constexpr (std::is_floating_point_v) { - std::cout << "浮点: " << v << "\n"; - } else { - std::cout << "其他类型\n"; - } -} - -// 使用 -// printValue(42); // 输出 整型: 42 -// printValue(3.14); // 输出 浮点: 3.14 - -``` - -Advantage: We only need one template to handle multiple types. The logic is centralized, and adding new branches is straightforward. - ------- - -### Case: Replacing SFINAE / enable_if (Cleaning Up Complex Specializations) - -Scenario: Using `.size()` for types that support `T::size()`, falling back to other implementations otherwise. - -```cpp -#include -#include - -// 检测有无 size()(简单版) -template -constexpr bool has_size_v = false; - -template -constexpr bool has_size_v().size())>> = true; - -template -auto getSizeIfPossible(const T& t) { - if constexpr (has_size_v) { - return t.size(); // 只有在 T 有 size() 时才编译 - } else { - return std::size_t{0}; // 备用实现 - } -} - -``` - -Explanation: With traditional SFINAE or `enable_if`, we would need to write multiple overloads or specializations, increasing both the code volume and maintenance costs. - ------- - -### Case: Compile-Time Recursion (`constexpr` + `if constexpr`) - -Usage: Compile-time computation (e.g., metaprogramming or constant generation). - -```cpp -constexpr uint64_t factorial(uint64_t n) { - if constexpr (n <= 1) { - return 1; - } else { - return n * factorial(n - 1); - } -} - -// constexpr auto f6 = factorial(6); // 720,编译期已计算 - -``` - -Note: The `if constexpr` here is paired with the `constexpr` function, and the result is evaluated at compile time (if the usage context allows). diff --git a/documents/en/vol4-advanced/04-non-type-template-params.md b/documents/en/vol4-advanced/04-non-type-template-params.md deleted file mode 100644 index 9493526d8..000000000 --- a/documents/en/vol4-advanced/04-non-type-template-params.md +++ /dev/null @@ -1,1837 +0,0 @@ ---- -chapter: 12 -cpp_standard: -- 11 -- 14 -- 17 -- 20 -description: In-depth Understanding of the Usage and Embedded Applications of C++ - Non-Type Template Parameters -difficulty: intermediate -order: 4 -platform: host -prerequisites: -- 'Chapter 12: 模板入门概述' -- 'Chapter 12: 函数模板详解' -reading_time_minutes: 53 -tags: -- cpp-modern -- host -- intermediate -title: Non-type template parameters ---- -# Modern C++ for Embedded Systems Tutorial — Non-Type Template Parameters - -## Introduction: Compile-Time Magic Numbers - -In embedded development, we frequently deal with various "magic numbers"—register addresses, bitmasks, buffer sizes, baud rates, and so on. The traditional approach uses macros or constants: - -```cpp -#define UART0_BASE 0x4000C000 -#define BUFFER_SIZE 256 -#define BAUD_RATE 115200 - -uint8_t buffer[BUFFER_SIZE]; -``` - -This approach has several problems: - -1. **No type safety**: Macros are purely text substitution, so the compiler performs no type checking. -2. **Difficult debugging**: Macros are replaced during preprocessing, so the original names are invisible in the debugger. -3. **Scope chaos**: Macros have no scope and easily collide with one another. -4. **No compile-time computation**: We cannot perform complex compile-time calculations based on these values. - -C++ non-type template parameters offer a more elegant solution. They allow us to use **compile-time constants** as template parameters, achieving zero-overhead abstraction while maintaining type safety. - ------- - -## Non-Type Template Parameter Basics - -### What Are Non-Type Template Parameters? - -Template parameters can be not only types (`typename T`) but also compile-time constant values: - -```cpp -template // T是类型参数,N是非类型参数 -class Array { - T data[N]; -}; -``` - -**Key characteristics**: - -- Non-type template parameters are **compile-time constants**. -- Their values must be determined at the point of template instantiation. -- The compiler generates distinct template instances for different values. - -### Supported Types - -The C++ standard restricts the types allowed for non-type template parameters: - -| C++ Standard | Supported Types | -|-------------|-----------------| -| C++11/14 | Integers, enumerations, pointers, references, pointers to members | -| C++17 | Adds automatic type deduction (`auto`) | -| C++20 | Adds floating-point types, class objects (subject to specific conditions) | - -```cpp -// 整型 -template -struct IntConstant { }; - -// 枚举 -template -struct EnumConstant { }; - -// 指针 -template -struct PointerConstant { }; - -// 引用 -template -struct ReferenceConstant { }; - -// C++17: auto -template -struct AutoConstant { }; - -// C++20: 浮点型 -template -struct DoubleConstant { }; -``` - ------- - -## Integer Non-Type Template Parameters - -### Basic Usage - -Integers are the most commonly used type for non-type template parameters, including: - -- `bool`, `char`, `int`, `unsigned int`, etc. -- `std::size_t`, `int8_t`, `uint16_t`, and other fixed-width types - -```cpp -template -struct IntegralConstant { - static constexpr int value = Value; - using value_type = int; - - constexpr operator int() const { return value; } -}; - -// 使用 -using Five = IntegralConstant<5>; -static_assert(Five::value == 5); -``` - -### Compile-Time Array Sizes - -This is the most common application scenario for non-type template parameters: - -```cpp -template -class FixedArray { -public: - constexpr std::size_t size() const { return N; } - constexpr bool empty() const { return N == 0; } - - T& operator[](std::size_t index) { - return data[index]; - } - - const T& operator[](std::size_t index) const { - return data[index]; - } - - T* begin() { return data; } - T* end() { return data + N; } - const T* begin() const { return data; } - const T* end() const { return data + N; } - -private: - T data[N]; -}; - -// 使用 -FixedArray arr1; // 编译期确定大小,栈上分配 -FixedArray arr2; - -static_assert(arr1.size() == 10); -``` - -**Advantages**: - -- Size is determined at compile time, requiring no dynamic allocation. -- Bounds checking can be performed at compile time (when combined with `static_assert`). -- Memory layout is fully predictable. - -### Bitmask Generator - -In embedded development, we frequently need to manipulate specific bits in a register: - -```cpp -template -struct BitMask { - static_assert(BitPos < sizeof(RegisterType) * 8, "Bit position out of range"); - - static constexpr RegisterType mask = static_cast(1) << BitPos; - - // 设置位 - static constexpr RegisterType set(RegisterType reg) { - return reg | mask; - } - - // 清除位 - static constexpr RegisterType clear(RegisterType reg) { - return reg & ~mask; - } - - // 切换位 - static constexpr RegisterType toggle(RegisterType reg) { - return reg ^ mask; - } - - // 测试位 - static constexpr bool is_set(RegisterType reg) { - return (reg & mask) != 0; - } -}; - -// 使用场景:GPIO配置 -using GPIO_Pin5 = BitMask; -using GPIO_Pin13 = BitMask; - -uint32_t gpio_mode = 0; -gpio_mode = GPIO_Pin5::set(gpio_mode); // 设置第5位 -gpio_mode = GPIO_Pin13::clear(gpio_mode); // 清除第13位 - -if (GPIO_Pin5::is_set(gpio_mode)) { - // 第5位已设置 -} -``` - -### Multi-Bit Masks - -```cpp -template -struct BitFieldMask { - static_assert(HighBit >= LowBit, "High bit must be >= Low bit"); - static_assert(HighBit < sizeof(RegisterType) * 8, "High bit out of range"); - - static constexpr RegisterType width = HighBit - LowBit + 1; - static constexpr RegisterType mask = ((static_cast(1) << width) - 1) << LowBit; - - // 提取位域 - static constexpr RegisterType extract(RegisterType reg) { - return (reg & mask) >> LowBit; - } - - // 设置位域 - static constexpr RegisterType set(RegisterType reg, RegisterType value) { - return (reg & ~mask) | ((value << LowBit) & mask); - } -}; - -// 使用:UART波特率设置(通常在特定位域) -using UART_BaudField = BitFieldMask; - -uint32_t uart_ctrl = 0; -uart_ctrl = UART_BaudField::set(uart_ctrl, 115200); // 设置波特率 -``` - -### Compile-Time Lookup Table - -```cpp -template -class LookupTable { -public: - constexpr LookupTable(const T (&values)[Size]) { - for (std::size_t i = 0; i < Size; ++i) { - data[i] = values[i]; - } - } - - constexpr T operator[](std::size_t index) const { - return data[index]; - } - - constexpr std::size_t size() const { return Size; } - -private: - T data[Size]; -}; - -// 编译期生成正弦表 -constexpr double generate_sin(std::size_t i, std::size_t total) { - return std::sin(2.0 * 3.14159265358979323846 * i / total); -} - -template -struct SineTable { - constexpr SineTable() : values{} { - for (std::size_t i = 0; i < Size; ++i) { - values[i] = generate_sin(i, Size); - } - } - - double values[Size]; -}; - -constexpr SineTable<256> sine_table; // 编译期生成256点正弦表 -``` - -### Limitations and Caveats - -**Integer limitations**: - -1. **Must be a compile-time constant** - - ```cpp - constexpr int N = 10; - FixedArray arr1; // OK - - int M = 20; - FixedArray arr2; // 错误:M不是编译期常量 - ``` - -2. **Size limitations** - - ```cpp - template - class LargeBuffer { - uint8_t data[N]; // N不能太大,否则栈溢出 - }; - - LargeBuffer<1024> buf1; // OK - LargeBuffer<1024*1024> buf2; // 可能栈溢出 - ``` - -3. **Signedness issues** - - ```cpp - template // 有符号 - struct SignedBuf { }; - - template // 无符号 - struct UnsignedBuf { }; - - SignedBuf<-1> sb; // OK - // UnsignedBuf<-1> ub; // 错误:无符号不能为负 - ``` - ------- - -## Pointer and Reference Type Parameters - -### Pointer Type Parameters - -Pointer type parameters allow us to bind to global objects at compile time: - -```cpp -// 全局数组 -constexpr char device_name[] = "STM32F407"; - -template -struct Device { - static constexpr const char* get_name() { return Name; } -}; - -// 使用 -using MyDevice = Device; -static_assert(std::string_view{MyDevice::get_name()} == "STM32F407"); -``` - -**Note**: Pointer parameters must point to objects with external linkage (global/static variables). - -### A More Practical Scenario for Pointer Parameters: Register Address Mapping - -This is one of the most important applications of non-type template parameters in embedded systems: - -```cpp -// 寄存器基类 -template -struct Register { - using value_type = RegType; - - // 读寄存器 - static RegType read() { - return *reinterpret_cast(Address); - } - - // 写寄存器 - static void write(RegType value) { - *reinterpret_cast(Address) = value; - } - - // 设置位 - static void set(RegType mask) { - *reinterpret_cast(Address) |= mask; - } - - // 清除位 - static void clear(RegType mask) { - *reinterpret_cast(Address) &= ~mask; - } - - // 地址访问 - static constexpr uintptr_t address() { return Address; } -}; - -// STM32 GPIO寄存器定义 -using GPIOA_BASE = Register; -using GPIOB_BASE = Register; - -// GPIO模式寄存器偏移 -template -struct GPIO_MODER : public Register { - using BaseReg = Base; - static constexpr uintptr_t offset = Offset; -}; - -// 使用 -using GPIOA_MODER = GPIO_MODER; - -// 配置PA5为输出(GPIO模式寄存器中第10-11位) -void config_pa5_output() { - constexpr uint32_t mode_mask = 0x3 << 10; // PA5对应位 - constexpr uint32_t output_mode = 0x1 << 10; - uint32_t current = GPIOA_MODER::read(); - GPIOA_MODER::write((current & ~mode_mask) | output_mode); -} -``` - -### Reference Type Parameters - -Reference type parameters are similar to pointer parameters but provide more natural syntax: - -```cpp -constexpr int global_config = 42; - -template -struct ConfigReader { - static int get() { return Config; } - static void set(int value) { Config = value; } -}; - -// 使用 -using MyConfig = ConfigReader; -``` - -### Pointers to Members - -Although less commonly used, these can sometimes be useful: - -```cpp -struct SensorData { - int temperature; - int humidity; - int pressure; -}; - -template -struct SensorField { - static int read(const SensorData& data) { - return data.*Member; - } - - static void write(SensorData& data, int value) { - data.*Member = value; - } -}; - -// 使用 -using TempField = SensorField<&SensorData::temperature>; -using HumidField = SensorField<&SensorData::humidity>; - -SensorData data{}; -TempField::write(data, 25); // 设置温度 -``` - -### Limitations - -**Limitations of pointer/reference parameters**: - -1. **Must point to/reference global/static objects** - - ```cpp - template - struct PtrHolder { }; - - int global_var; - static int static_var; - - void func() { - int local_var; - PtrHolder<&global_var> h1; // OK - PtrHolder<&static_var> h2; // OK - // PtrHolder<&local_var> h3; // 错误:局部变量 - } - ``` - -2. **Special nature of string literals** - - ```cpp - template - struct StringHolder { }; - - // 字符串字面量具有内部链接,C++中需要特殊处理 - extern constexpr char str[] = "hello"; // extern赋予外部链接 - StringHolder sh; // OK - ``` - -3. **Special handling of nullptr** - - ```cpp - template - struct PtrTest { }; - - // PtrTest pt; // 错误:nullptr不能直接用于指针模板参数 - // 正确做法:使用0或nullptr_t模板参数 - template - struct NullPtrTest { }; - - NullPtrTest npt; // OK - ``` - ------- - -## C++17: auto Non-Type Template Parameters - -### Basic Syntax - -C++17 introduced `auto` as a non-type template parameter type, letting the compiler perform automatic deduction: - -```cpp -template -struct AutoConstant { - static constexpr auto value = Value; - using value_type = decltype(Value); -}; - -// 使用 -using IntVal = AutoConstant<42>; // 推导为int -using CharVal = AutoConstant<'x'>; // 推导为char -using UintVal = AutoConstant<123U>; // 推导为unsigned int -using PtrVal = AutoConstant; // 推导为std::nullptr_t -``` - -### Type Deduction Rules - -```cpp -template -void print_value() { - std::cout << Value << " (type: " << typeid(decltype(Value)).name() << ")\n"; -} - -print_value<42>(); // int -print_value<42L>(); // long -print_value<42U>(); // unsigned int -print_value<3.14>(); // double -print_value<'a'>(); // char -``` - -### Practical Application: Generic Value Wrapper - -```cpp -template -struct ValueHolder { - constexpr static auto value = Value; - constexpr operator decltype(Value)() const { return Value; } - - // 基于值类型的操作 - constexpr auto square() const { - return Value * Value; - } -}; - -// 使用 -constexpr auto v1 = ValueHolder<10>{}; -static_assert(v1.value == 10); -static_assert(v1.square() == 100); -``` - -### Type-Safe Enum Wrapper - -```cpp -enum class Color : uint8_t { - Red = 0, - Green = 1, - Blue = 2 -}; - -template -struct ColorInfo { - static_assert(std::is_same_v, - "Must be a Color enum value"); - - static constexpr Color color = ColorValue; - static constexpr const char* name() { - if constexpr (color == Color::Red) return "Red"; - else if constexpr (color == Color::Green) return "Green"; - else if constexpr (color == Color::Blue) return "Blue"; - else return "Unknown"; - } -}; - -// 使用 -using RedColor = ColorInfo; -static_assert(std::string_view{RedColor::name()} == "Red"); -``` - -### Compile-Time Value Sequences - -```cpp -template -struct ValueSequence { - static constexpr std::size_t size = sizeof...(Values); - - template - static constexpr auto get = std::get(std::make_tuple(Values...)); -}; - -// 使用 -using MySequence = ValueSequence<1, 2.0, 'c', nullptr>; -static_assert(MySequence::size == 4); -static_assert(MySequence::get<0> == 1); -``` - -### Limitations of auto Parameters - -```cpp -// ❌ 不能作为函数参数的默认模板参数 -template // C++17允许,但使用时需要注意 -struct DefaultAuto { }; - -DefaultAuto<> da; // OK - -// ❌ 不能推导复杂的类型 -struct MyType { int x; }; -// template // 错误:非字面量类型 -// struct ComplexAuto { }; - -// ✅ C++20后,字面量类类型也可以使用 -template -struct LiteralAuto { }; - -// C++20允许,如果MyType是字面量类型 -struct MyType { - int x; - constexpr MyType(int v) : x(v) {} -}; - -// LiteralAuto la; // C++20: OK -``` - ------- - -## Practical Example: Compile-Time Array Size Utilities - -### Problem Background - -In C/C++, arrays decay to pointers when passed as function arguments, losing their size information: - -```cpp -void func(int arr[]) { - // sizeof(arr)是指针大小,不是数组大小! - // 无法获取原始数组大小 -} - -int data[100]; -func(data); // 丢失大小信息 -``` - -The traditional C macro approach has safety issues: - -```cpp -#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) - -// 问题:对指针使用会得到错误结果 -int* ptr = nullptr; -size_t wrong = ARRAY_SIZE(ptr); // 编译通过,但结果错误 -``` - -### Template Solution - -```cpp -// 基础版本 -template -constexpr std::size_t array_size(T (&)[N]) noexcept { - return N; -} - -// 使用 -int data[42]; -constexpr auto size = array_size(data); // 42,编译期常量 - -// array_size(ptr); // 编译错误!指针不能匹配 -``` - -### Enhanced Version: Type-Safe Array Utilities - -```cpp -template -struct ArrayInfo { - using value_type = T; - using pointer = T*; - using const_pointer = const T*; - using reference = T&; - using const_reference = const T&; - using size_type = std::size_t; - - static constexpr size_type size = N; - static constexpr size_type bytes = sizeof(T) * N; - - // 编译期边界检查 - template - static constexpr size_type safe_index = Index < N ? Index : - throw "Index out of bounds"; // 编译期错误 - - // 元素访问(编译期索引) - template - static constexpr reference get(T (&arr)[N]) { - static_assert(Index < N, "Index out of bounds"); - return arr[Index]; - } - - // 字节大小 - static constexpr size_type byte_size() noexcept { - return bytes; - } -}; - -// 使用 -int data[10]; -using Info = ArrayInfo; - -static_assert(Info::size == 10); -static_assert(Info::bytes == 40); -static_assert(Info::safe_index<5> == 5); -// static_assert(Info::safe_index<15> == 15); // 编译错误 -``` - -### Multi-Dimensional Array Support - -```cpp -// 一维数组 -template -constexpr std::size_t array_size(T (&)[N]) noexcept { - return N; -} - -// 二维数组 -template -constexpr std::size_t array_size(T (&)[M][N]) noexcept { - return M * N; -} - -// 通用版:返回总元素数 -template -constexpr std::size_t array_total_size(T (&)[N]) noexcept { - return N; -} - -template -constexpr std::size_t array_total_size(T (&)[M][Ns...]) noexcept { - return M * array_total_size(((T(&)[Ns...])nullptr)[0]); -} - -// 获取维度 -template -struct ArrayRank; - -template -struct ArrayRank : std::integral_constant {}; - -template -struct ArrayRank : std::integral_constant::value> {}; - -// 使用 -int arr2d[3][4]; -static_assert(array_size(arr2d) == 3 * 4); // 总元素数 -static_assert(ArrayRank::value == 2); // 维度 -``` - -### Compile-Time Array Iterator - -```cpp -template -class ArrayIterator { -public: - constexpr ArrayIterator(T (&arr)[N], std::size_t pos = 0) - : array_(arr), pos_(pos) {} - - constexpr T& operator*() const { - return array_[pos_]; - } - - constexpr ArrayIterator& operator++() { - ++pos_; - return *this; - } - - constexpr ArrayIterator operator++(int) { - auto tmp = *this; - ++pos_; - return tmp; - } - - constexpr bool operator!=(const ArrayIterator& other) const { - return pos_ != other.pos_; - } - -private: - T (&array_)[N]; - std::size_t pos_; -}; - -template -struct ArrayRange { - T (&array)[N]; - - constexpr ArrayIterator begin() const { - return {array, 0}; - } - - constexpr ArrayIterator end() const { - return {array, N}; - } -}; - -// 使用 -int data[5] = {1, 2, 3, 4, 5}; -constexpr auto range = ArrayRange{data}; - -// 编译期遍历 -constexpr bool all_positive = [] { - for (auto it = range.begin(); it != range.end(); ++it) { - if (*it <= 0) return false; - } - return true; -}(); -static_assert(all_positive); -``` - -### Complete Example: Embedded Buffer Utilities - -::: details Click to expand the complete compile-time buffer utility code - -```cpp -#include -#include - -// 基础工具 -template -constexpr std::size_t array_size(T (&)[N]) noexcept { - return N; -} - -// 编译期缓冲区类 -template -class AlignedBuffer { - alignas(Alignment) T data_[Size]; -public: - using value_type = T; - using size_type = std::size_t; - using pointer = T*; - using const_pointer = const T*; - - static constexpr size_type size = Size; - static constexpr size_type alignment = Alignment; - static constexpr size_type bytes = sizeof(T) * Size; - - // 编译期初始化为特定值 - constexpr AlignedBuffer() : data_{} {} - - constexpr AlignedBuffer(T init_value) : data_{} { - for (size_type i = 0; i < Size; ++i) { - data_[i] = init_value; - } - } - - // 元素访问 - constexpr T& operator[](size_type index) { - return data_[index]; - } - - constexpr const T& operator[](size_type index) const { - return data_[index]; - } - - // 指针访问 - constexpr pointer data() { return data_; } - constexpr const_pointer data() const { return data_; } - - // 填充 - constexpr void fill(T value) { - for (size_type i = 0; i < Size; ++i) { - data_[i] = value; - } - } - - // 比较编译期大小 - template - constexpr bool size_equals() const { - return Size == OtherSize; - } -}; - -// 环形缓冲区(编译期容量) -template -class CircularBuffer { - T data_[Capacity]; - std::size_t head_ = 0; - std::size_t tail_ = 0; - bool full_ = false; - -public: - static constexpr std::size_t capacity = Capacity; - using value_type = T; - - constexpr CircularBuffer() : data_{} {} - - constexpr bool push(T value) { - data_[head_] = value; - if (full_) { - tail_ = (tail_ + 1) % Capacity; - } - head_ = (head_ + 1) % Capacity; - full_ = (head_ == tail_); - return true; - } - - constexpr bool pop(T& value) { - if (empty()) return false; - value = data_[tail_]; - full_ = false; - tail_ = (tail_ + 1) % Capacity; - return true; - } - - constexpr bool empty() const { - return !full_ && (head_ == tail_); - } - - constexpr bool full() const { - return full_; - } - - constexpr std::size_t size() const { - if (full_) return Capacity; - return (head_ >= tail_) ? (head_ - tail_) : - (Capacity + head_ - tail_); - } - - constexpr void clear() { - head_ = tail_ = 0; - full_ = false; - } -}; - -// 使用示例 -void buffer_example() { - // 对齐缓冲区 - AlignedBuffer dma_buffer; // 16字节对齐,用于DMA - AlignedBuffer uart_rx; - - // 环形缓冲区 - CircularBuffer rx_ring; - rx_ring.push(0x01); - rx_ring.push(0x02); - - uint8_t data; - while (rx_ring.pop(data)) { - // 处理数据 - } -} - -``` - -::: - ------- - -## Practical Example: Bitmask Generator - -### Compile-Time Bit Manipulation Utility Set - -```cpp - -#include - -namespace bits { - -// 基础位掩码 -template -struct Bit { - static_assert(Bit < sizeof(RegType) * 8, "Bit out of range"); - static constexpr RegType mask = RegType{1} << Bit; - - static constexpr RegType set(RegType reg) { return reg | mask; } - static constexpr RegType clear(RegType reg) { return reg & ~mask; } - static constexpr RegType toggle(RegType reg) { return reg ^ mask; } - static constexpr bool is_set(RegType reg) { return (reg & mask) != 0; } -}; - -// 多位掩码 -template -struct Bits { - static_assert(High >= Low, "High must be >= Low"); - static constexpr RegType width = High - Low + 1; - static constexpr RegType mask = ((RegType{1} << width) - 1) << Low; - - static constexpr RegType extract(RegType reg) { - return (reg & mask) >> Low; - } - - static constexpr RegType set(RegType reg, RegType value) { - return (reg & ~mask) | ((value << Low) & mask); - } -}; - -// 编译期计算位掩码值 -template -struct BitMask { - static constexpr RegType value = ((RegType{1} << Bits) | ...); -}; - -// 寄存器定义辅助 -template -struct RegisterField { - static constexpr RegType offset = Offset; - static constexpr RegType mask = ((RegType{1} << Bits) | ...); - - template - static constexpr RegType read() { - return *reinterpret_cast(BaseAddr::address + offset); - } - - template - static constexpr void write(RegType value) { - *reinterpret_cast(BaseAddr::address + offset) = value; - } - - template - static constexpr void set(RegType value) { - write(read() | (value << offset)); - } -}; - -} // namespace bits - -// 使用示例 -using namespace bits; - -// 定义LED位掩码 -using LED1 = Bit; -using LED2 = Bit; -using LED3 = Bit; - -void gpio_example() { - uint32_t gpio_odr = 0; - - // 设置LED - gpio_odr = LED1::set(gpio_odr); - gpio_odr = LED2::set(gpio_odr); - - // 测试LED状态 - if (LED1::is_set(gpio_odr)) { - // LED1打开 - } - - // 切换LED - gpio_odr = LED3::toggle(gpio_odr); - - // 清除LED - gpio_odr = LED2::clear(gpio_odr); -} -``` - -### Compile-Time Bit Field Extractor - -```cpp -// 通信协议中的位域解析 -template -struct BitField { - static_assert(BitWidth > 0 && BitWidth <= 64, "Invalid bit width"); - static_assert(BitOffset >= 0 && BitOffset < 8, "Invalid bit offset"); - - using value_type = std::conditional_t>>; - - static constexpr int total_bit_offset = ByteOffset * 8 + BitOffset; - static constexpr T mask = ((T{1} << BitWidth) - 1) << total_bit_offset; - - static constexpr value_type extract(const T* buffer) { - return static_cast( - (buffer[ByteOffset] >> BitOffset) & ((1 << BitWidth) - 1) - ); - } - - static constexpr void encode(T* buffer, value_type value) { - buffer[ByteOffset] &= ~(((1 << BitWidth) - 1) << BitOffset); - buffer[ByteOffset] |= (value & ((1 << BitWidth) - 1)) << BitOffset; - } -}; - -// 使用:CAN消息解析 -uint8_t can_msg[8]; - -// 假设某信号位于字节2的第3位,长度5位 -using CanSignal = BitField; - -auto signal_value = CanSignal::extract(can_msg); // 提取信号 -CanSignal::encode(can_msg, 0x1F); // 编码信号 -``` - -### SPI/I2C Configuration Generator - -```cpp -// SPI配置参数 -template -struct SPIConfig { - static_assert(Prescaler >= 2 && Prescaler <= 256, "Invalid prescaler"); - static_assert(CPOL == 0 || CPOL == 1, "CPOL must be 0 or 1"); - static_assert(CPHA == 0 || CPHA == 1, "CPHA must be 0 or 1"); - static_assert(BitOrder == 0 || BitOrder == 1, "BitOrder must be MSB_FIRST(0) or LSB_FIRST(1)"); - - // 计算控制寄存器值(简化版) - static constexpr uint32_t cr1_value = - (BitOrder << 7) | - (CPHA << 0) | - (CPOL << 1) | - ((Prescaler / 2 - 1) << 3); // 假设prescaler编码 -}; - -// 预定义配置 -using SPI_FullSpeed_CPOL0_CPHA0 = SPIConfig<2, 0, 0, 0>; -using SPI_HalfSpeed_CPOL1_CPHA1 = SPIConfig<4, 1, 1, 0>; - -// I2C速率配置 -template -struct I2CTiming { - static constexpr int standard_freq = 100000; // 100kHz - static constexpr int fast_freq = 400000; // 400kHz - static constexpr int fast_plus_freq = 1000000; // 1MHz - - static constexpr bool is_standard = ClockRate == standard_freq; - static constexpr bool is_fast = ClockRate == fast_freq; - static constexpr bool is_fast_plus = ClockRate == fast_plus_freq; - - // 计算时序寄存器值(简化) - static constexpr uint32_t timingr_value = - (is_standard ? 0x00000000 : is_fast ? 0x00600000 : 0x00100000); -}; - -using I2C_StandardMode = I2CTiming<100000, 150, 50>; -using I2C_FastMode = I2CTiming<400000, 100, 30>; -``` - ------- - -## Embedded Application: Compile-Time Encapsulation of Register Addresses - -### Type-Safe Register Access - -```cpp -#include -#include - -namespace hal { - -// 寄存器地址标记类型 -template -struct RegisterAddress { - uintptr_t address; - constexpr explicit RegisterAddress(uintptr_t addr) : address(addr) {} -}; - -// 寄存器基类 -template -class Register { -public: - using value_type = RegType; - - explicit constexpr Register(RegisterAddress addr) - : address_(reinterpret_cast(addr.address)) {} - - // 读操作 - constexpr RegType read() const volatile { - return *address_; - } - - // 写操作 - constexpr void write(RegType value) volatile { - *address_ = value; - } - - // 设置位 - constexpr void set(RegType mask) volatile { - *address_ |= mask; - } - - // 清除位 - constexpr void clear(RegType mask) volatile { - *address_ &= ~mask; - } - - // 切换位 - constexpr void toggle(RegType mask) volatile { - *address_ ^= mask; - } - - // 读-修改-写 - constexpr void modify(RegType clear_mask, RegType set_mask) volatile { - *address_ = (*address_ & ~clear_mask) | set_mask; - } - -private: - volatile RegType* const address_; -}; - -// 标签类型(防止类型混淆) -struct GPIOA_Tag {}; -struct GPIOB_Tag {}; -struct UART1_Tag {}; -struct SPI1_Tag {}; - -// 基地址定义 -namespace base { - constexpr inline RegisterAddress GPIOA{0x40020000}; - constexpr inline RegisterAddress GPIOB{0x40020400}; - constexpr inline RegisterAddress UART1{0x40011000}; - constexpr inline RegisterAddress SPI1{0x40013000}; -} - -// 具体寄存器定义(使用偏移量) -template -using GPIO_Register = Register; - -} // namespace hal -``` - -### Peripheral Definition Framework - -```cpp -namespace stm32f4 { - -// GPIO端口定义 -template -struct GPIO_Port { - using MODER = Register{0x40020000}>; - using OTYPER = Register{0x40020004}>; - using OSPEEDR = Register{0x40020008}>; - using PUPDR = Register{0x4002000C}>; - using IDR = Register{0x40020010}>; - using ODR = Register{0x40020014}>; - using BSRR = Register{0x40020018}>; - - // 模式定义 - enum Mode : uint32_t { - Input = 0, - Output = 1, - Alternate = 2, - Analog = 3 - }; - - // 输出类型 - enum OutputType : uint16_t { - PushPull = 0, - OpenDrain = 1 - }; - - // 速度 - enum Speed : uint32_t { - Low = 0, - Medium = 1, - High = 2, - VeryHigh = 3 - }; - - // 上下拉 - enum Pull : uint32_t { - None = 0, - Up = 1, - Down = 2 - }; - - // 引脚配置辅助函数 - template - static void config_mode(Mode mode) { - constexpr uint32_t mask = 0x3 << (Pin * 2); - MODER reg{RegisterAddress{0x40020000}}; - reg.modify(mask, static_cast(mode) << (Pin * 2)); - } - - template - static void config_output(OutputType type) { - OTYPER reg{RegisterAddress{0x40020004}}; - if (type == OpenDrain) { - reg.set(1 << Pin); - } else { - reg.clear(1 << Pin); - } - } -}; - -// 具体端口类型别名 -using GPIOA = GPIO_Port; -using GPIOB = GPIO_Port; - -} // namespace stm32f4 - -// 使用示例 -void gpio_init() { - using namespace stm32f4; - - // 配置PA5为输出(板载LED) - GPIOA::config_mode<5>(GPIOA::Output); - GPIOA::config_output<5>(GPIOA::PushPull); - - // 控制LED - GPIOA::ODR odr{RegisterAddress{0x40020014}}; - odr.set(1 << 5); // 点亮 - odr.clear(1 << 5); // 熄灭 -} -``` - -### UART Configuration Example - -```cpp -namespace stm32f4 { - -template -struct UART { - using CR1 = Register{0x0}>; - using CR2 = Register{0x4}>; - using BRR = Register{0x8}>; - - // 波特率计算(简化版) - template - static constexpr uint32_t calculate_brr() { - return (ClockFreq + BaudRate / 2) / BaudRate; - } - - // 编译期配置 - template - static void config() { - constexpr uint32_t brr_value = calculate_brr(); - - BRR brr{RegisterAddress{0x40011000 + 0x8}}; - brr.write(brr_value); - - CR1 cr1{RegisterAddress{0x40011000}}; - uint32_t cr1_value = (1 << 13); // UE: UART使能 - cr1_value |= (1 << 2); // RE: 接收使能 - cr1_value |= (1 << 3); // TE: 发送使能 - - if constexpr (EnableParity) { - cr1_value |= (1 << 10); // PCE: 奇偶校验使能 - if constexpr (!Even) { - cr1_value |= (1 << 9); // PS: 奇校验 - } - } - - cr1.write(cr1_value); - } -}; - -using UART1 = UART; - -} // namespace stm32f4 - -// 使用:在编译期配置UART -void uart_init() { - // 假设APB2时钟84MHz,配置为115200波特率 - // 这些值都在编译期计算 - stm32f4::UART1::config<84000000, 115200>(); -} -``` - -### Compile-Time Pin Mapping - -```cpp -// 引脚映射配置 -template -struct Pin { - static constexpr int port = Port; - static constexpr int pin = Pin; - - // 编译期生成引脚号 - static constexpr int pin_number = Port * 16 + Pin; - - // 编译期生成位掩码 - static constexpr uint32_t mask = 1 << Pin; -}; - -// 引脚别名 -using PA5 = Pin<0, 5>; // GPIOA, Pin 5 -using PB13 = Pin<1, 13>; // GPIOB, Pin 13 -using PC9 = Pin<2, 9>; // GPIOC, Pin 9 - -// LED定义 -using LED = PA5; - -// 编译期检查 -static_assert(LED::pin_number == 5); -static_assert(LED::mask == 0x20); - -// 引脚功能配置 -template -struct AlternateFunction { - static_assert(Pin::pin < 16, "Invalid pin number"); - - // AFR寄存器:pin 0-7使用AFRL,pin 8-15使用AFRH - static constexpr bool use_high = Pin::pin >= 8; - static constexpr int reg_offset = use_high ? 4 : 0; - static constexpr int shift = (Pin::pin % 8) * 4; - static constexpr uint32_t mask = 0xF << shift; - - static constexpr uint32_t afr_value = static_cast(AlternateFunc::value) << shift; -}; - -// SPI引脚配置示例 -enum class SPI_AF : uint32_t { - AF5 = 5, // SPI1/2 - AF6 = 6 // SPI3 -}; - -using SPI1_SCK = AlternateFunction, SPI_AF::AF5>; // PA5 -> AF5 -using SPI1_MISO = AlternateFunction, SPI_AF::AF5>; // PA6 -> AF5 -using SPI1_MOSI = AlternateFunction, SPI_AF::AF5>; // PA7 -> AF5 -``` - -### Complete Example: DMA Configuration - -::: details Click to expand the DMA compile-time configuration example - -```cpp -namespace stm32f4 { - -// DMA通道定义 -template -struct DMA { - static_assert(Stream >= 0 && Stream < 8, "Invalid DMA stream"); - static_assert(Channel >= 0 && Channel < 8, "Invalid DMA channel"); - - static constexpr uintptr_t base = 0x40026000; - static constexpr uintptr_t stream_base = base + Stream * 0x10 + 0x10 * (Stream / 4); - - using CR = Register{stream_base + 0x00}>; - using NDTR = Register{stream_base + 0x04}>; - using PAR = Register{stream_base + 0x08}>; - using M0AR = Register{stream_base + 0x0C}>; - using M1AR = Register{stream_base + 0x10}>; - - // 方向 - enum Direction : uint32_t { - PeripheralToMemory = 0, - MemoryToPeripheral = 1, - MemoryToMemory = 2 - }; - - // 数据宽度 - enum Width : uint32_t { - Byte = 0, - HalfWord = 1, - Word = 2 - }; - - // 编译期配置 - template< - Direction Dir, - Width MemWidth = Word, - Width PeriphWidth = Word, - bool MemInc = true, - bool PeriphInc = false, - bool Circular = false - > - static void config() { - constexpr uint32_t cr_value = - (Channel << 25) | // 通道选择 - (Dir << 6) | // 方向 - (MemInc << 9) | // 内存递增 - (PeriphInc << 8) | // 外设递增 - (MemWidth << 10) | // 内存宽度 - (PeriphWidth << 8) | // 外设宽度 - (Circular << 5); // 循环模式 - - CR cr{RegisterAddress{stream_base}}; - cr.write(cr_value); - } - - // 启动传输 - static void enable() { - CR cr{RegisterAddress{stream_base}}; - cr.set(1 << 0); // EN位 - } - - static void disable() { - CR cr{RegisterAddress{stream_base}}; - cr.clear(1 << 0); - } -}; - -// 预定义DMA配置 -using UART1_TX_DMA = DMA<4, 4>; // Stream 4, Channel 4 - -void uart_dma_send_example() { - uint8_t tx_buffer[256]; - uintptr_t uart1_dr = 0x40011004; - - // 配置DMA - UART1_TX_DMA::config< - DMA<4, 4>::MemoryToPeripheral, - DMA<4, 4>::Byte, - DMA<4, 4>::Byte, - true, false - >(); - - // 设置地址 - UART1_TX_DMA::NDTR ndtr{RegisterAddress{UART1_TX_DMA::stream_base + 0x04}}; - ndtr.write(256); - - UART1_TX_DMA::PAR par{RegisterAddress{UART1_TX_DMA::stream_base + 0x08}}; - par.write(uart1_dr); - - UART1_TX_DMA::M0AR m0ar{RegisterAddress{UART1_TX_DMA::stream_base + 0x0C}}; - m0ar.write(reinterpret_cast(tx_buffer)); - - // 启动 - UART1_TX_DMA::enable(); -} - -} // namespace stm32f4 - -``` - -::: - ------- - -## Common Pitfalls and Solutions - -### Pitfall 1: Floating-Point Types as Non-Type Template Parameters - -**Problem**: Floating-point types were not supported as non-type template parameters before C++20: - -```cpp - -// C++17之前:错误 -template -struct DoubleConstant { }; - -// C++17之前的解决方案:转换为整数比例 -template -struct Ratio { - static constexpr double value = static_cast(Numerator) / Denominator; -}; - -using PiApprox = Ratio<314159, 100000>; // 3.14159 -``` - -**C++20 solution**: - -```cpp -// C++20:直接支持浮点类型 -template -struct DoubleConstant { - static constexpr double value = Value; -}; - -using Pi = DoubleConstant<3.14159265358979323846>; -``` - -### Pitfall 2: Class Objects as Non-Type Template Parameters - -**Before C++20**: Class objects could not be used as non-type template parameters: - -```cpp -// ❌ C++17错误 -struct Config { - int value; - Config(int v) : value(v) {} -}; - -template -struct ConfigHolder { }; -``` - -**C++20 solution**: The class type must be a "literal type": - -```cpp -// ✅ C++20:字面类型 -struct Config { - int value; - constexpr Config(int v) : value(v) {} -}; - -// constexpr变量可以作为模板参数 -constexpr Config my_config{42}; - -template -struct ConfigHolder { - static constexpr int config_value = C.value; -}; - -using MyHolder = ConfigHolder; -static_assert(MyHolder::config_value == 42); -``` - -### Pitfall 3: Duplicate Instantiations Caused by Different Template Parameter Types - -```cpp -template -struct Buffer { - uint8_t data[N]; -}; - -Buffer<10> b1; // 实例化1 -Buffer<010> b2; // 实例化2(八进制10 = 十进制8)! -Buffer<0xA> b3; // 错误:非整型字面量 -Buffer<10> b4; // 与b1共享实例 - -// 解决方案:使用强类型包装 -template -struct Size { - static constexpr std::size_t value = N; -}; - -template -struct TypedBuffer { - uint8_t data[S::value]; -}; - -using Size10 = Size<10>; -TypedBuffer tb1; -TypedBuffer> tb2; // 同一类型 -``` - -### Pitfall 4: Confusion Between Compile-Time Constants and Runtime Values - -```cpp -template -void process(int (&arr)[N]) { - // N是编译期常量 - std::array arr_copy; // OK - - std::size_t runtime_size = get_size(); - // std::array arr2; // 错误:需要编译期常量 -} - -// 解决方案:使用constexpr函数返回编译期值 -constexpr std::size_t get_buffer_size() { - return 256; -} - -std::array buffer; // OK -``` - -### Pitfall 5: Linkage Issues - -```cpp -// 头文件 A.h -template -struct A { }; - -// 源文件 a.cpp -const char hello[] = "hello"; - -// 头文件 B.h -template -struct B { }; - -// 问题:字符串字面量具有内部链接 -// template struct C { }; -// C<"hello"> c; // 错误:某些编译器不允许 - -// 解决方案:使用extern -extern constexpr char world[] = "world"; -A a; // OK -``` - -### Pitfall 6: Dependent Name Lookup for Template Parameters - -```cpp -template -struct Factorial { - static constexpr int value = N * Factorial::value; -}; - -template<> -struct Factorial<0> { - static constexpr int value = 1; -}; - -// 问题:某些编译器需要显式指定模板参数 -template -struct Factorial2 { - static constexpr int value = N * ::Factorial2::value; // 加::限定 -}; - -// 更好的解决方案:使用别名模板 -template -using Factor = Factorial; - -constexpr int result = Factor<5>::value; -``` - ------- - -## C++20 New Features - -### Floating-Point Non-Type Template Parameters - -```cpp -template -struct DoubleParam { - static constexpr double value = Value; - constexpr double operator()() const { return Value; } -}; - -// 使用 -constexpr DoubleParam<3.14159> pi; -constexpr auto pi_value = pi(); -static_assert(pi_value > 3.14 && pi_value < 3.15); - -// 应用:编译期三角函数 -template -struct SinTable { - static constexpr double value = std::sin(Angle); -}; - -constexpr auto sin_30 = SinTable<3.14159265358979323846 / 6>::value; -static_assert(sin_30 > 0.49 && sin_30 < 0.51); -``` - -### Literal Type Non-Type Template Parameters - -```cpp -struct Point { - int x, y; - constexpr Point(int x_, int y_) : x(x_), y(y_) {} - constexpr bool operator==(const Point& other) const { - return x == other.x && y == other.y; - } -}; - -template -struct PointHolder { - static constexpr int x = P.x; - static constexpr int y = P.y; -}; - -// 使用 -constexpr Point origin{0, 0}; -using Origin = PointHolder; - -static_assert(Origin::x == 0); -static_assert(Origin::y == 0); -``` - -### Class Template Argument Deduction (CTAD) for Non-Type Template Parameters - -```cpp -template -struct ValueWrapper { - constexpr static auto value = Value; -}; - -// CTAD -ValueWrapper w1{42}; // 推导为ValueWrapper -ValueWrapper w2{3.14}; // 推导为ValueWrapper -ValueWrapper w3{'a'}; // 推导为ValueWrapper -``` - -### Constraining Non-Type Template Parameters with Concepts - -```cpp -#include - -template -struct IntegralBuffer { - static constexpr auto size = N; - int data[N]; -}; - -template -struct FloatingWrapper { - static constexpr auto value = F; -}; - -IntegralBuffer<10> ib; // OK -// IntegralBuffer<3.14> ib2; // 错误:不是整数类型 - -FloatingWrapper<2.718> fw; // OK -// FloatingWrapper<42> fw2; // 错误:不是浮点类型 -``` - -### Three-Way Comparison Support - -```cpp -template -requires requires { { Value } <=> std::declval(); } -struct OrderedValue { - constexpr static auto value = Value; - - constexpr auto operator<=>(const OrderedValue&) const = default; -}; - -OrderedValue<10> v1; -OrderedValue<20> v2; -static_assert(v1 < v2); -``` - ------- - -## Summary - -Non-type template parameters are a powerful and unique feature of the C++ template system, particularly well-suited for embedded development: - -### Key Takeaways - -| Feature | Description | Embedded Application | -|---------|-------------|----------------------| -| Integer parameters | Compile-time constant integers | Array sizes, bitmasks, baud rates | -| Pointer parameters | Compile-time bound addresses | Register mapping, memory addresses | -| Reference parameters | Compile-time bound references | Global configuration, device descriptors | -| auto parameters (C++17) | Automatically deduced types | Generic value wrappers | -| Floating-point parameters (C++20) | Compile-time floating-point constants | Trigonometric tables, physical constants | - -### Practical Recommendations - -1. **Prioritize type safety** - - Use strongly typed wrappers instead of macros. - - Use `static_assert` for compile-time validation. - - Use tag types to prevent type confusion. - -2. **Compile-time computation** - - Move runtime calculations to compile time. - - Use `constexpr` functions to generate template parameters. - - Leverage compile-time constants for optimization. - -3. **Code organization** - - Group related non-type template parameters into configuration structures. - - Use alias templates to simplify complex types. - - Create predefined types for common configurations. - -4. **Performance considerations** - - Non-type template parameters add no runtime overhead. - - However, overuse can lead to code bloat. - - Share template instances judiciously. - -### Checklist - -When using non-type template parameters, ensure: - -- [ ] The parameter type can be determined at compile time. -- [ ] Pointer/reference parameters point to objects with external linkage. -- [ ] Array sizes are within a reasonable range. -- [ ] Appropriate `static_assert` checks are added. -- [ ] Type aliases are provided for common configurations. -- [ ] Documentation specifies the valid range for each parameter. - -**In the next chapter**, we will explore **class templates**, learning how to implement generic containers, understand the special rules for template member functions, and dive deep into the advanced applications of template specialization and partial specialization. diff --git a/documents/en/vol4-advanced/05-template-args-and-name-lookup.md b/documents/en/vol4-advanced/05-template-args-and-name-lookup.md deleted file mode 100644 index 7ffab2c2f..000000000 --- a/documents/en/vol4-advanced/05-template-args-and-name-lookup.md +++ /dev/null @@ -1,1471 +0,0 @@ ---- -title: Template parameter dependent name lookup -description: In-depth Understanding of Two-Phase Lookup and Dependent Name Handling - in C++ Templates -chapter: 12 -order: 5 -tags: -- cpp-modern -- host -- intermediate -difficulty: intermediate -reading_time_minutes: 40 -prerequisites: -- 'Chapter 12: 模板入门概述' -- 'Chapter 12: 函数模板详解' -cpp_standard: -- 11 -- 14 -- 17 -- 20 -platform: host ---- -# Modern C++ for Embedded Systems Tutorial — Template Argument Dependence and Name Lookup - -Have you ever run into this puzzle: a piece of template code that looks perfectly correct, yet the compiler complains that it cannot find a certain type? Or when calling a function inside a template, the function clearly exists, but the compiler insists it is undefined? - -Welcome to the most headache-inducing part of C++ templates — **name lookup**. Understanding two-phase lookup, dependent names, and ADL is a rite of passage for becoming a C++ template expert. - ------- - -## Dependent Names - -### What Are Dependent Names? - -In template code, a **dependent name** is a name whose meaning depends on a template parameter. When the compiler parses a template, it cannot yet determine what these names represent — because the template parameters are still unknown. - -```cpp -template -void process(T container) { - // value_type 是依赖名称——它的类型取决于 T - typename T::value_type* ptr; - - // clear 是依赖名称——它是否存在取决于 T - container.clear(); -} -``` - -**Key point**: When parsing the template definition, the compiler does not yet know what type `T` is, so it cannot determine whether `T::value_type` is a type, a member variable, or a static function. - -### Categories of Dependent Names - -| Category | Description | Example | -|------|------|------| -| Dependent type names | Types that depend on template parameters | `T::value_type`, `T::iterator` | -| Dependent expression names | Expressions that depend on template parameters | `t.get()`, `x + y` | -| Non-dependent names | Names that do not depend on template parameters | `int`, `std::vector` | - -```cpp -template -void example(T t) { - int x = 0; // 非依赖类型 - std::vector v; // 非依赖类型 - - typename T::type y; // 依赖类型 - t.method(); // 依赖表达式(method是否存在取决于T) - t + x; // 依赖表达式(operator+是否存在取决于T) -} -``` - -### The Necessity of the typename Keyword - -For **dependent type names**, the C++ standard requires the `typename` keyword to explicitly tell the compiler "this is a type." - -```cpp -template -void func(T container) { - // ❌ 错误:编译器不知道 T::value_type 是类型还是成员 - T::value_type* ptr; - - // ✅ 正确:用 typename 明确指出这是一个类型 - typename T::value_type* ptr; -} -``` - -**Why is this keyword needed?** - -Because C++ grammar itself is ambiguous. Consider these two cases: - -```cpp -// 情况1:value_type 是一个类型 -struct MyContainer { - using value_type = int; -}; - -// 情况2:value_type 是一个静态成员变量 -struct AnotherContainer { - static int value_type; -}; - -template -void ambiguous() { - T::value_type * p; - // 这是声明一个指向 T::value_type 类型的指针 p? - // 还是将 T::value_type(一个变量)与 p 相乘? -} -``` - -With `typename` added, the ambiguity is resolved: - -```cpp -template -void unambiguous() { - typename T::value_type* p; // 明确:声明指针 -} -``` - -### Rules for Using typename - -**Rule 1**: Whenever you use `T::XXX` in a template and want it to be a type, you must add `typename` - -```cpp -template -void process(T container) { - // 获取迭代器类型 - typename T::iterator it = container.begin(); - - // 获取值类型 - typename T::value_type val = *it; - - // 嵌套的情况 - typename T::template Inner::type x; -} -``` - -**Rule 2**: `typename` is only needed in contexts that depend on template parameters - -```cpp -template -class MyClass { - // 这里不需要 typename,因为不在模板函数体中 - using value_type = typename T::value_type; - - void method() { - // 这里需要 typename - typename T::some_type* ptr; - } -}; -``` - -**Rule 3**: Certain contexts do not need `typename` (the compiler knows it must be a type) - -```cpp -template -class Derived : public T::Base { // 基类列表不需要 typename - // ... -}; - -template -void func() { - // 类型转换不需要 typename - typedef typename T::type Type; - Type* p = static_cast(some_ptr); -} -``` - -### The Necessity of the template Keyword - -Similarly, when accessing a dependent member template, we need to use the `template` keyword: - -```cpp -template -void process(T container) { - // ❌ 错误:编译器不知道 < 是小于号还是模板参数列表开始 - auto ptr = container.get_ptr(); - - // ✅ 正确:用 template 明确指出这是成员模板 - auto ptr = container.template get_ptr(); -} -``` - -**Complete example**: - -```cpp -template -struct Allocator { - template - struct rebind { - using other = Allocator; - }; -}; - -template -void example(Allocator alloc) { - // 获取 rebind::other - // 需要 typename(因为 rebind::other 是依赖类型) - // 需要 template(因为 rebind 是成员模板) - using ReboundAlloc = typename T::template rebind::other; -} -``` - -**When to use the template keyword**: - -| Scenario | Required? | Example | -|------|----------|------| -| Accessing a member type | Requires `typename` | `typename T::type` | -| Accessing a member template | Requires `template` | `obj.template foo()` | -| Accessing an ordinary member | Not required | `obj.method()` | -| Accessing a static member | Not required | `T::static_var` | - ------- - -## Two-Phase Lookup - -### What Is Two-Phase Lookup? - -When a C++ compiler processes template code, it performs name lookup in two phases: - -| Phase | Timing | What Is Looked Up | Error Handling | -|------|------|----------|----------| -| **Phase 1** | When parsing the template definition | Non-dependent names | Immediate error | -| **Phase 2** | When instantiating the template | Dependent names | Error at the point of instantiation | - -```cpp -template -void func(T t) { - // 阶段1检查:非依赖名称 foo 必须可见 - foo(t); - - // 阶段2检查:依赖名称 t.bar() 在实例化时才检查 - t.bar(); -} - -void foo(int); // 必须在模板定义之前可见 - -// 使用 -func(42); // 阶段2:检查 int 是否有 bar() 方法 -``` - -### Phase 1: Template Definition Time - -During this phase, the compiler: - -1. Checks that the template syntax is correct -2. Looks up all **non-dependent names** -3. Validates the use of `typename` and `template` keywords - -```cpp -// 正确示例 -template -void example1(T t) { - std::cout << t; // OK:std::cout 是非依赖名称,必须可见 -} - -// 错误示例 -template -void example2(T t) { - // 错误:print 未声明(即使 T 有 print 方法也不行) - // print 是非依赖名称,必须在定义时可见 - print(t); - - // 如果想调用 T 的方法,必须用 t.print() - t.print(); // OK:依赖名称,阶段2检查 -} -``` - -### Phase 2: Template Instantiation Time - -During this phase, the compiler: - -1. Substitutes concrete template arguments for `T` -2. Looks up all **dependent names** -3. Checks that dependent operations are valid - -```cpp -template -void process(T t) { - // 阶段1:operator<< 非依赖,必须有 std::operator<< 可见 - std::cout << t; - - // 阶段2:clear 是依赖名称,实例化时才检查 - t.clear(); -} - -struct A { void clear() {} }; -struct B { }; // 没有 clear 方法 - -process(A{}); // OK:A 有 clear 方法 -process(B{}); // 错误:B 没有 clear 方法(阶段2错误) -``` - -### Practical Example of Two-Phase Lookup - -```cpp -#include - -// 函数必须在模板定义之前定义 -void helper() { - std::cout << "Helper called\n"; -} - -template -void wrapper(T t) { - helper(); // 非依赖:必须在定义时可见 - t.method(); // 依赖:实例化时检查 -} - -// 如果把 helper 放在这里,编译失败! -// void helper() { } - -int main() { - wrapper(42); // 阶段2:检查 int 是否有 method() -} -``` - -**Common error**: - -```cpp -// ❌ 错误示例 -template -void func(T t) { - using namespace std; // 在模板内部 using namespace - cout << t; // 依赖名称?非依赖名称?不明确! -} - -// ✅ 正确做法 -template -void func(T t) { - std::cout << t; // 明确的非依赖名称 -} - -// 或者 -template -void func(T t) { - using std::cout; // 明确引入 - cout << t; -} -``` - -### Interaction Between Two-Phase Lookup and ADL - -```cpp -namespace MyNS { - struct A {}; - void foo(A); // ADL 候选 -} - -template -void call_foo(T t) { - foo(t); // 阶段1:检查 foo 是否存在 - // 阶段2:ADL 可能找到 MyNS::foo -} - -int main() { - MyNS::A a; - call_foo(a); // 通过 ADL 找到 MyNS::foo -} -``` - -**Key point**: Lookup for dependent names considers ADL, while lookup for non-dependent names does not. - -### Considerations for Embedded Development - -In embedded development, two-phase lookup can affect compilation time and error diagnostics: - -```cpp -// 嵌入式场景:外设访问模板 -template -void init_peripheral() { - // 阶段1检查:必须能看到这些 - using namespace MCU::Registers; - - // 阶段2检查:Periph 必须有这些成员 - Periph::enable_clock(); - Periph::reset(); -} - -// 使用 -struct UART1 { - static void enable_clock(); - static void reset(); -}; - -init_peripheral(); // 阶段2验证 -``` - ------- - -## ADL (Argument-Dependent Lookup) in Detail - -### What Is ADL? - -**Argument-Dependent Lookup** (ADL), also known as **Koenig lookup**, is a special name lookup rule. It allows the compiler to search for functions in the namespaces of the argument types. - -```cpp -namespace MyNS { - struct A {}; - void do_something(A); // 定义在 MyNS 中 -} - -int main() { - MyNS::A a; - do_something(a); // 无需前缀!ADL 自动找到 MyNS::do_something -} -``` - -### Basic Rules of ADL - -**Rule 1**: When making a function call, the compiler searches in the following places: - -1. The current scope -2. Enclosing scopes (ordinary lookup) -3. The namespaces of the argument types (ADL) - -```cpp -namespace NS1 { - struct X {}; - void func(X); // NS1::func -} - -namespace NS2 { - struct Y {}; - void func(Y); // NS2::func -} - -void test() { - NS1::X x; - NS2::Y y; - func(x); // ADL 找到 NS1::func - func(y); // ADL 找到 NS2::func -} -``` - -**Rule 2**: ADL only works for functions, not for class templates - -```cpp -namespace MyNS { - struct A {}; - template void func(T); - template class MyClass; // 类模板不支持 ADL -} - -int main() { - MyNS::A a; - func(a); // OK:ADL 工作于函数模板 - - // MyClass obj; // 错误:必须写 MyNS::MyClass -} -``` - -**Rule 3**: ADL ignores using directives - -```cpp -namespace Lib { - struct X {}; - void lib_func(X); -} - -namespace App { - using namespace Lib; // using 指令 - - void test() { - X x; - lib_func(x); // ADL 找到 Lib::lib_func - } -} -``` - -### ADL and Operator Overloading - -The most important application of ADL is operator overloading: - -```cpp -namespace Math { - struct Vector { - double x, y; - }; - - Vector operator+(Vector a, Vector b) { - return {a.x + b.x, a.y + b.y}; - } -} - -int main() { - using Math::Vector; - - Vector v1{1, 2}; - Vector v2{3, 4}; - - // v1 + v2 实际上调用 operator+(v1, v2) - // 通过 ADL 找到 Math::operator+ - Vector v3 = v1 + v2; // 无需 Math::operator+! -} -``` - -**This is why we can write `a + b` directly without needing to write `operator+(a, b)` or `SomeNS::operator+(a, b)`.** - -### Special ADL Rules in Templates - -In templates, ADL rules become more complex: - -```cpp -namespace NS { - struct A {}; - void foo(A); -} - -template -void call_foo(T t) { - foo(t); // 阶段1:foo 必须可见(至少声明) - // 阶段2:通过 ADL 查找 -} - -// 在模板定义点,foo 必须至少被声明 -void foo(...); // 前向声明即可 - -int main() { - NS::A a; - call_foo(a); // 通过 ADL 找到 NS::foo -} -``` - -### ADL and Friend Functions - -```cpp -class MyClass { - friend void friend_func(MyClass); // 友元声明 - -private: - int secret = 42; -}; - -// 定义(可以是外部的) -void friend_func(MyClass obj) { - std::cout << obj.secret; // 可以访问私有成员 -} - -int main() { - MyClass obj; - friend_func(obj); // ADL 找到友元函数 -} -``` - -### ADL Pitfalls and Considerations - -**Pitfall 1: Name Hiding** - -```cpp -namespace Lib { - struct X {}; - void process(X); -} - -namespace App { - void process(void*); // 不同签名 - - void test() { - Lib::X x; - process(x); // 错误:只找到 App::process,不匹配 Lib::X - // ADL 找到的 Lib::process 被隐藏 - } -} -``` - -**Solution**: Explicitly use the namespace - -```cpp -void test() { - Lib::X x; - Lib::process(x); // 明确调用 -} -``` - -**Pitfall 2: Infinite Recursion** - -```cpp -namespace NS { - struct X; - void to_string(X); - - struct X { - // 错误:无限递归 - std::string str() { - return to_string(*this); // 调用自己 - } - }; -} -``` - -**Pitfall 3: Special Nature of the std Namespace** - -Certain standard library functions do not participate in ADL: - -```cpp -namespace std { - struct string {}; - void some_func(string); // 假设的函数 -} - -int main() { - std::string s; - some_func(s); // 不一定通过 ADL 找到 - // 添加到 std 是未定义行为! -} -``` - -**Important**: Never add anything to the `std` namespace! - -### ADL in Practice: Custom Iterators - -```cpp -namespace MyContainer { - template - class Container { - public: - class iterator { - T* ptr; - public: - iterator(T* p) : ptr(p) {} - iterator& operator++() { ++ptr; return *this; } - T& operator*() { return *ptr; } - }; - - iterator begin() { return iterator(data_); } - iterator end() { return iterator(data_ + size_); } - - private: - T data_[100]; - std::size_t size_ = 0; - }; - - // 自定义 begin/end 以支持 ADL - template - typename Container::iterator begin(Container& c) { - return c.begin(); - } - - template - typename Container::iterator end(Container& c) { - return c.end(); - } -} - -int main() { - using namespace MyContainer; - Container c; - - // 通过 ADL 找到 begin/end - for (auto& x : c) { - // ... - } -} -``` - -### ADL Summary Table - -| Scenario | Does ADL Work? | Example | -|------|-------------|------| -| Ordinary functions | Yes | `func(obj)` finds `NS::func` | -| Operators | Yes | `a + b` finds `NS::operator+` | -| Class templates | No | `MyClass` requires a fully qualified path | -| Member functions | N/A | `obj.method()` does not involve ADL | -| Namespace aliases | Yes | Can still be triggered through an aliased type | - ------- - -## In Practice: Writing Correct Generic Iterator Code - -Let's put our knowledge to use and write a correct, robust generic iterator. - -### Requirements Analysis - -Our iterator needs to: - -1. Support arbitrary container types -2. Correctly handle dependent type names -3. Support ADL for ease of use -4. Provide type-safe access - -### Basic Implementation - -```cpp -template -class GenericIterator { -public: - // 依赖类型:必须使用 typename - using value_type = typename Container::value_type; - using reference = typename Container::reference; - using pointer = typename Container::pointer; - using difference_type = typename Container::difference_type; - -private: - Container& container_; - std::size_t index_; - -public: - explicit GenericIterator(Container& c, std::size_t index = 0) - : container_(c), index_(index) {} - - // 解引用 - reference operator*() { - return container_[index_]; - } - - // 箭头运算符 - pointer operator->() { - return &container_[index_]; - } - - // 前置递增 - GenericIterator& operator++() { - ++index_; - return *this; - } - - // 后置递增 - GenericIterator operator++(int) { - auto temp = *this; - ++index_; - return temp; - } - - // 比较 - bool operator==(const GenericIterator& other) const { - return index_ == other.index_; - } - - bool operator!=(const GenericIterator& other) const { - return !(*this == other); - } -}; -``` - -### Enhanced Version: Supporting Containers Without Standard Typedefs - -Some containers might not provide standard typedefs like `value_type`, so we need to use SFINAE (Substitution Failure Is Not An Error): - -```cpp -// 类型萃取辅助 -template -struct container_traits { - // 默认情况下,假设有标准 typedef - using value_type = typename T::value_type; -}; - -// 针对 C 数组的特化 -template -struct container_traits { - using value_type = T; - using reference = T&; - using pointer = T*; -}; - -template -class AdvancedIterator { - // 使用萃取获取类型,提供合理的默认值 - using traits = container_traits; - using value_type = typename traits::value_type; - -private: - Container& container_; - std::size_t index_; - -public: - explicit AdvancedIterator(Container& c, std::size_t index = 0) - : container_(c), index_(index) {} - - // 类型安全的访问 - value_type& operator*() { - return container_[index_]; - } - - value_type* operator->() { - return &container_[index_]; - } - - // ... 其他运算符 -}; -``` - -### Complete Generic Access Function - -```cpp -// 获取容器大小(支持数组和容器) -template -constexpr std::size_t size(T (&)[N]) noexcept { - return N; -} - -template -constexpr auto size(const Container& c) noexcept -> decltype(c.size()) { - return c.size(); -} - -// 获取迭代器 -template -auto begin(Container& c) -> decltype(c.begin()) { - return c.begin(); -} - -template -T* begin(T (&arr)[N]) { - return arr; -} - -template -auto end(Container& c) -> decltype(c.end()) { - return c.end(); -} - -template -T* end(T (&arr)[N]) { - return arr + N; -} -``` - -### Embedded Application: Generic Register Access Iterator - -```cpp -namespace MCU { - // 寄存器访问基类 - template - class RegisterIterator { - volatile RegType* base_; - std::size_t offset_; - - public: - using value_type = RegType; - using reference = RegType&; - using pointer = volatile RegType*; - - RegisterIterator(volatile RegType* base, std::size_t offset) - : base_(base), offset_(offset) {} - - reference operator*() const { - return const_cast(base_[offset_]); - } - - pointer operator->() const { - return base_ + offset_; - } - - RegisterIterator& operator++() { - ++offset_; - return *this; - } - - RegisterIterator operator++(int) { - auto temp = *this; - ++offset_; - return temp; - } - - bool operator==(const RegisterIterator& other) const { - return offset_ == other.offset_; - } - - bool operator!=(const RegisterIterator& other) const { - return !(*this == other); - } - }; - - // GPIO 端口访问 - template - class GPIOPort { - public: - using iterator = RegisterIterator; - - static constexpr std::size_t register_count = 16; - - static volatile std::uint32_t* base() { - return reinterpret_cast(PortBase); - } - - iterator begin() { - return iterator(base(), 0); - } - - iterator end() { - return iterator(base(), register_count); - } - }; -} - -// 使用示例 -void clear_all_gpio() { - constexpr std::size_t GPIOA_BASE = 0x40020000; - MCU::GPIOPort port; - - for (auto& reg : port) { - reg = 0; // 清零所有寄存器 - } -} -``` - -### Constrained Iterators (C++20 Concepts) - -```cpp -template -concept Iterable = requires(T t) { - typename T::value_type; - { t.begin() } -> std::same_as; - { t.end() } -> std::same_as; -}; - -template -class SafeIterator { - using value_type = typename Container::value_type; - // ... -}; - -// 使用 -SafeIterator> it1; // OK -// SafeIterator it2; // 编译错误 -``` - -### Complete Example: Generic Print Function - -```cpp -#include -#include -#include - -// 检查是否有 value_type -template -struct has_value_type : std::false_type {}; - -template -struct has_value_type> - : std::true_type {}; - -// 泛型打印函数 -template -auto print_container(const Container& c) - -> std::enable_if_t::value, void> -{ - std::cout << "["; - bool first = true; - for (const auto& item : c) { - if (!first) std::cout << ", "; - std::cout << item; - first = false; - } - std::cout << "]\n"; -} - -// C数组特化 -template -void print_container(const T (&arr)[N]) { - std::cout << "["; - for (std::size_t i = 0; i < N; ++i) { - if (i > 0) std::cout << ", "; - std::cout << arr[i]; - } - std::cout << "]\n"; -} - -// 使用 -int main() { - std::vector vec = {1, 2, 3, 4, 5}; - std::array arr = {1.1, 2.2, 3.3}; - int c_arr[] = {10, 20, 30}; - - print_container(vec); // [1, 2, 3, 4, 5] - print_container(arr); // [1.1, 2.2, 3.3] - print_container(c_arr); // [10, 20, 30] -} -``` - -::: details Complete runnable example: generic iterator toolkit - -```cpp -#include -#include -#include -#include - -namespace generic { - -// ============ 类型萃取 ============ -template -struct container_traits { - using value_type = typename T::value_type; - using size_type = typename T::size_type; - using reference = typename T::reference; - using const_reference = typename T::const_reference; -}; - -// C数组特化 -template -struct container_traits { - using value_type = T; - using size_type = std::size_t; - using reference = T&; - using const_reference = const T&; -}; - -// ============ 容器访问函数 ============ - -// size 函数 -template -constexpr std::size_t size(T (&)[N]) noexcept { - return N; -} - -template -constexpr auto size(const Container& c) noexcept -> decltype(c.size()) { - return c.size(); -} - -// begin 函数(ADL 友好) -template -auto begin(Container& c) -> decltype(c.begin()) { - return c.begin(); -} - -template -auto begin(const Container& c) -> decltype(c.begin()) { - return c.begin(); -} - -template -T* begin(T (&arr)[N]) { - return arr; -} - -template -const T* begin(const T (&arr)[N]) { - return arr; -} - -// end 函数(ADL 友好) -template -auto end(Container& c) -> decltype(c.end()) { - return c.end(); -} - -template -auto end(const Container& c) -> decltype(c.end()) { - return c.end(); -} - -template -T* end(T (&arr)[N]) { - return arr + N; -} - -template -const T* end(const T (&arr)[N]) { - return arr + N; -} - -// ============ 泛型遍历 ============ - -template -void for_each(Container&& c, Func&& func) { - using std::begin; - using std::end; - auto first = begin(c); - auto last = end(c); - for (; first != last; ++first) { - func(*first); - } -} - -// ============ 累加器 ============ - -template -auto sum(const Container& c) -> typename container_traits::value_type { - using value_type = typename container_traits::value_type; - value_type result{}; - for_each(c, [&result](const value_type& v) { result += v; }); - return result; -} - -// ============ 打印器 ============ - -namespace detail { - struct print_fn { - template - void operator()(const T& v) const { - std::cout << v; - } - }; -} - -template -void print(const Container& c) { - std::cout << "["; - bool first = true; - for_each(c, [&](const auto& v) { - if (!first) std::cout << ", "; - detail::print_fn{}(v); - first = false; - }); - std::cout << "]"; -} - -} // namespace generic - -// ============ 使用示例 ============ -int main() { - using namespace generic; - - std::vector vec = {1, 2, 3, 4, 5}; - std::array arr = {1.1, 2.2, 3.3}; - int c_arr[] = {10, 20, 30}; - - std::cout << "Vector: "; - print(vec); - std::cout << ", size=" << size(vec) << ", sum=" << sum(vec) << "\n"; - - std::cout << "Array: "; - print(arr); - std::cout << ", size=" << size(arr) << ", sum=" << sum(arr) << "\n"; - - std::cout << "C-array: "; - print(c_arr); - std::cout << ", size=" << size(c_arr) << ", sum=" << sum(c_arr) << "\n"; - - // 使用 for_each - std::cout << "Squared: "; - for_each(vec, [](int x) { std::cout << x * x << " "; }); - std::cout << "\n"; - - return 0; -} - -``` - -::: - ------- - -## Common Pitfalls: Why Doesn't `t.clear()` Work Sometimes? - -### Pitfall 1: Dependent Names and Two-Phase Lookup - -```cpp - -template -void process(T t) { - // 阶段1:编译器检查 clear 是否作为非依赖名称存在 - // 阶段2:编译器检查 T 是否有 clear 方法 - - t.clear(); // 如果 T 没有 clear(),阶段2报错 -} - -struct HasClear { - void clear() { std::cout << "Cleared!\n"; } -}; - -struct NoClear {}; // 没有 clear 方法 - -int main() { - process(HasClear{}); // OK - process(NoClear{}); // 错误:NoClear 没有 clear 成员 -} -``` - -**Solution**: Use SFINAE (Substitution Failure Is Not An Error) or Concepts constraints - -```cpp -// C++17 SFINAE -template -auto process_safe(T t) -> decltype(t.clear(), void()) { - t.clear(); -} - -// C++20 Concepts -template -concept Clearable = requires(T t) { t.clear(); }; - -template -void process_concept(T t) { - t.clear(); -} -``` - -### Pitfall 2: ADL and Name Hiding - -```cpp -namespace Lib { - struct Data {}; - void process(Data d) { std::cout << "Lib::process\n"; } -} - -namespace App { - void process(void*) { std::cout << "App::process\n"; } - - void test() { - Lib::Data d; - process(d); // 错误!只找到 App::process - // Lib::process 被"隐藏"了 - } -} -``` - -**Why does this happen?** - -Because ordinary name lookup finds `App::process` in the current scope, and even if it does not match, ADL will not continue searching. - -**Solution 1**: Explicitly call the namespace - -```cpp -void test() { - Lib::Data d; - Lib::process(d); // 明确指定 -} -``` - -**Solution 2**: Introduce it into the current scope - -```cpp -namespace App { - using Lib::process; // 引入 - void process(void*) { std::cout << "App::process\n"; } - - void test() { - Lib::Data d; - process(d); // 现在可以找到 Lib::process - } -} -``` - -### Pitfall 3: typename in the Wrong Position - -```cpp -template -struct MyClass { - // 错误:这里不需要 typename(不在函数体中) - using type = typename T::value_type; - - void method() { - // 正确:这里需要 typename - typename T::some_type* ptr; - } -}; - -// 更复杂的错误 -template -void func() { - // 错误:嵌套的成员模板需要 template 关键字 - // T::template Inner::type* ptr; - - // 正确写法 - typename T::template Inner::type* ptr; -} -``` - -### Pitfall 4: operator+ and ADL - -```cpp -namespace MyMath { - struct Vector { double x, y; }; - Vector operator+(Vector a, Vector b) { - return {a.x + b.x, a.y + b.y}; - } -} - -template -void add_and_print(T a, T b) { - // 这里依赖 ADL 找到 operator+ - auto c = a + b; // OK:通过 ADL 找到 MyMath::operator+ - std::cout << c.x << ", " << c.y << "\n"; -} - -int main() { - MyMath::Vector v1{1, 2}; - MyMath::Vector v2{3, 4}; - add_and_print(v1, v2); // 正常工作 -} -``` - -But if we write it like this: - -```cpp -template -void broken_add(T a, T b) { - using std::operator+; // 错误做法! - auto c = a + b; // 可能找不到 MyMath::operator+ -} -``` - -### Pitfall 5: Friend Functions and ADL - -```cpp -class SecretHolder { - int secret = 42; - friend void reveal(const SecretHolder&); // 友元声明 -}; - -// 必须在命名空间作用域定义 -void reveal(const SecretHolder& s) { - std::cout << s.secret << "\n"; // OK:友元可以访问 -} - -int main() { - SecretHolder s; - reveal(s); // 通过 ADL 找到友元函数 -} -``` - -But if we place the definition inside the class: - -```cpp -class SecretHolder { - int secret = 42; - friend void reveal(const SecretHolder& s) { - std::cout << s.secret << "\n"; // 不是成员函数! - } -}; - -int main() { - SecretHolder s; - reveal(s); // 错误!类内定义的友元不参与普通名字查找 -} -``` - -**Solution**: A friend defined inside a class can only be found via ADL, but an in-class definition is not visible at the call site. We need a forward declaration or an external definition. - -### Pitfall 6: Members of Template Base Classes Are Not Visible - -```cpp -template -struct Base { - void method() { std::cout << "Base::method\n"; } - using value_type = T; -}; - -template -struct Derived : Base { - void use_method() { - // method(); // 错误!编译器不查找依赖基类 - - // 解决方案1:使用 this-> 令其成为依赖名称 - this->method(); - - // 解决方案2:使用 using 声明 - using Base::method; - method(); - - // 解决方案3:完全限定名 - Base::method(); - } - - void use_type() { - // value_type x; // 错误! - - typename Base::value_type x; // 正确 - } -}; -``` - -**Explanation**: Because `Base` is a dependent base class (it depends on a template parameter), the compiler will not look up its members during phase 1. We must use `this->` or a fully qualified name to make it a dependent name. - -### Pitfall Reference Table - -| Pitfall | Cause | Solution | -|------|------|----------| -| `t.clear()` does not work | `T` has no `clear` method | SFINAE/Concepts constraints | -| Cannot find a function with the same name | Name hiding | Explicit namespace or `using` | -| `typename` in the wrong position | Non-function-body types do not need it | Only use it at dependent types | -| Accessing member templates | Missing the `template` keyword | Use `obj.template foo()` | -| Base class members are not visible | Dependent base class lookup rules | Use `this->` or `using` | -| Friend functions are not visible | ADL rule restrictions | Ensure definition is at namespace scope | - -### Debugging Template Name Lookup Issues - -When encountering name lookup issues, follow these steps to troubleshoot: - -1. **Check if it is a dependent name**: Does the name depend on a template parameter? -2. **Check if `typename` is needed**: Is it a dependent type? -3. **Check two-phase lookup**: Are non-dependent names visible at the point of definition? -4. **Check ADL**: Is the function in the namespace of the argument types? -5. **Check base classes**: Is it a member of a dependent base class? -6. **Check constraints**: Are SFINAE (Substitution Failure Is Not An Error) or Concepts needed? - -```cpp -// 调试技巧:static_assert 提供清晰错误 -template -void debug_process(T t) { - using value_type = typename T::value_type; // 如果失败,给出明确错误 - static_assert(std::is_same_v, "Only int containers supported"); - // ... -} -``` - ------- - -## Summary - -Template argument dependence and name lookup are core mechanisms of C++ templates. Understanding them is crucial for writing correct template code. - -### Key Concepts Review - -| Concept | Core Idea | Use Case | -|------|----------|----------| -| **Dependent names** | Names that depend on template parameters | Accessing `T::XXX` in a template | -| **typename** | Indicates a dependent type | `typename T::value_type` | -| **template** | Indicates a dependent member template | `obj.template foo()` | -| **Two-phase lookup** | Look up non-dependent names at definition, dependent names at instantiation | Understanding when compilation errors occur | -| **ADL** | Looks up functions in the namespaces of argument types | Operator overloading, swap, etc. | - -### Practical Advice - -1. **Always use `typename` for dependent types** - - ```cpp - typename T::value_type* ptr; - ``` - -2. **Use the `template` keyword for dependent member templates** - - ```cpp - obj.template method(); - ``` - -3. **Understand two-phase lookup and organize code accordingly** - - ```cpp - // 非依赖名称:在模板定义前声明 - void helper(); - - template - void func(T t) { - helper(); // 非依赖:定义时检查 - t.method(); // 依赖:实例化时检查 - } - ``` - -4. **Leverage ADL to simplify function calls** - - ```cpp - // 用户可以写 swap(a, b) 而非 std::swap(a, b) - using std::swap; - swap(a, b); - ``` - -5. **Use Concepts to provide clear constraints** - - ```cpp - template - concept Clearable = requires(T t) { t.clear(); }; - - template - void process(T t) { t.clear(); } - ``` - -6. **Be aware of the special rules for dependent base class members** - - ```cpp - template - struct Derived : Base { - void foo() { - this->method(); // 使用 this-> 令其成为依赖名称 - } - }; - ``` - -### C++ Standard Evolution - -| Standard | New Feature | What It Simplified | -|------|--------|-----------| -| C++11 | `decltype` | More precise type deduction | -| C++14 | `decltype(auto)` | Automatic deduction of reference types | -| C++17 | `std::void_t` | Simpler SFINAE (Substitution Failure Is Not An Error) | -| C++17 | `if constexpr` | Compile-time branching | -| C++20 | Concepts | Clear template constraints | -| C++20 | `requires` | Better constraint expression | - -**In the next chapter**, we will explore **variadic templates**, learning how to write template functions that accept an arbitrary number of arguments, and implement a type-safe embedded event system. diff --git a/documents/en/vol4-advanced/06-template-friends-and-barton-nackman.md b/documents/en/vol4-advanced/06-template-friends-and-barton-nackman.md deleted file mode 100644 index ae0a74f6a..000000000 --- a/documents/en/vol4-advanced/06-template-friends-and-barton-nackman.md +++ /dev/null @@ -1,1344 +0,0 @@ ---- -title: Template friends and the Barton-Nackman trick -description: Mastering template techniques for friend injection and operator overloading -chapter: 12 -order: 6 -tags: -- cpp-modern -- host -- intermediate -difficulty: intermediate -reading_time_minutes: 35 -prerequisites: -- 'Chapter 12: 类模板详解' -- 'Chapter 12: 模板实例化与特化' -cpp_standard: -- 11 -- 14 -- 17 -- 20 -platform: host ---- -# Modern C++ for Embedded Systems Tutorial — Template Friends and the Barton-Nackman Trick - -Have you ever wondered why standard library ``std::complex`` or ``std::pair`` can be compared directly using ``==``? Why don't they need a bunch of operator functions defined in the global scope? The answer is **friend injection** and the **Barton-Nackman Trick**. - -This is an elegant template technique that not only makes operator overloading concise, but also serves as the precursor to CRTP (Curiously Recurring Template Pattern). This chapter dives deep into the mechanics behind this pattern and implements a fully functional, comparable ``Point`` type. - ------- - -## Basic Relationship Between Friend Functions and Templates - -Before understanding the Barton-Nackman Trick, we need to review the basic concept of friend functions in C++ and how they combine with templates. - -### Friend Functions in Ordinary Classes - -For non-template classes, defining friend operators is very straightforward: - -```cpp -class Point { - double x, y; -public: - Point(double x, double y) : x(x), y(y) {} - - // 友元函数声明 - friend bool operator==(const Point& a, const Point& b) { - return a.x == b.x && a.y == b.y; - } - - friend bool operator!=(const Point& a, const Point& b) { - return !(a == b); - } -}; - -// 使用 -Point p1{1, 2}, p2{3, 4}; -bool eq = (p1 == p2); // 正常工作 -``` - -In this approach, the friend function is defined inside the class but belongs to the outer scope (it can be called from outside the class). - -### The Friend Function Dilemma in Class Templates - -When we change ``Point`` to a template, problems arise: - -```cpp -template -class Point { - T x, y; -public: - Point(T x, T y) : x(x), y(y) {} - - // 尝试定义友元运算符 - friend bool operator==(const Point& a, const Point& b) { - return a.x == b.x && a.y == b.y; - } -}; - -// 使用 -Point p1{1, 2}; -Point p2{3, 4}; -// bool eq = (p1 == p2); // ❌ 链接错误!未定义的引用 -``` - -Why? Because the friend function of a class template is **not a template itself**, but rather a **non-template function** generated for each instantiated type. If the friend function is defined inside the class, it is inline and should theoretically work. However, in practice, some compilers might encounter issues at link time. - -More importantly, if we want ``Point`` and ``Point`` to be comparable as well, this approach falls short. - ------- - -## Friend Injection Mechanism - -Now let's introduce the core concept — **friend injection**. - -### What is Friend Injection? - -Friend injection means that when a friend function is defined inside a class template, it not only becomes a friend of the class, but is also **injected into the enclosing scope** (usually the global or namespace scope), and **can be found via Argument-Dependent Lookup (ADL)**. - -Key point: this friend function is **not a template function**, but a **non-template function**, though it can access the private members of the class. - -### Basic Syntax - -```cpp -template -class Point { - T x, y; -public: - Point(T x, T y) : x(x), y(y) {} - - // 友元注入:函数在类内定义,但可在外部通过ADL找到 - friend bool operator==(const Point& a, const Point& b) { - return a.x == b.x && a.y == b.y; - } -}; - -// 使用 -Point p1{1, 2}; -Point p2{1, 2}; - -// 通过ADL找到operator== -bool eq = (p1 == p2); // ✅ 正常工作 -// 等价于 bool eq = operator==(p1, p2); -// 但 operator== 不在全局作用域,只能通过ADL找到 -``` - -### The Crucial Role of ADL (Argument-Dependent Lookup) - -ADL is part of C++ name lookup rules: when calling a function, the compiler searches not only the current scope, but also the **namespace where the argument types are defined**. - -```cpp -namespace geometry { - template - class Point { - T x, y; - public: - Point(T x, T y) : x(x), y(y) {} - - // 友元函数 - friend bool operator==(const Point& a, const Point& b) { - return a.x == b.x && a.y == b.y; - } - }; -} - -// 使用 -geometry::Point p1{1, 2}, p2{1, 2}; - -// ❌ 如果写 operator==(p1, p2) 会找不到 -// ✅ 但写 p1 == p2 可以通过ADL找到 -bool eq = (p1 == p2); // ADL在geometry命名空间中查找operator== -``` - -### Three Key Characteristics of Friend Injection - -| Characteristic | Description | Example | -|------|------|------| -| Non-template function | Each instantiation generates a distinct non-template function | ``Point`` generates one ``operator==``, ``Point`` generates another | -| Inline definition | The function body must be defined inside the class | Cannot be declared inside and defined outside the class | -| ADL-findable | Can only be found through argument-dependent lookup | Directly writing ``operator==`` might not find it | - -```cpp -template -class Point { - // ... - friend bool operator==(const Point& a, const Point& b) { - return a.x == b.x && a.y == b.y; - } -}; - -Point p1, p2; -p1 == p2; // ✅ ADL找到 -// operator==(p1, p2); // ❌ 可能找不到(取决于编译器) -``` - ------- - -## Barton-Nackman Trick - -Now we arrive at the core of this chapter — the Barton-Nackman Trick (also known as "restricted template friend injection"). - -### Historical Background - -This trick was first described by John Barton and Lee Nackman in their 1994 book *Scientific and Engineering C++*. It is one of the earliest constrained generic programming techniques, and the ideological precursor to CRTP (Curiously Recurring Template Pattern) and C++20 Concepts. - -### Core Idea - -**Define a friend function template inside a class template, where the parameter types of the function template are constrained by the class template parameters.** - -```cpp -template -class Point { - T x, y; -public: - Point(T x, T y) : x(x), y(y) {} - - // Barton-Nackman Trick - // 友元函数模板,约束为只能比较相同类型的Point - friend bool operator==(const Point& a, const Point& b) { - return a.x == b.x && a.y == b.y; - } -}; -``` - -Wait, how is this different from the friend injection we saw earlier? The key difference is: **the ``operator==`` here is a function template**, not a non-template function. - -### Correct Barton-Nackman Syntax - -To truly define a friend function template, we need to explicitly declare the template parameters: - -```cpp -template -class Point { - T x, y; -public: - Point(T x, T y) : x(x), y(y) {} - - // 方式1:非模板友元(之前讲过的) - friend bool operator==(const Point& a, const Point& b) { - return a.x == b.x && a.y == b.y; - } - - // 方式2:真正的 Barton-Nackman - 友元函数模板 - template - friend bool operator==(const Point& a, const Point& b) { - return a.x == b.x && a.y == b.y; - } -}; -``` - -In practice, however, approach 1 (non-template friend) is sufficient for most scenarios and is the approach recommended in modern C++. Approach 2 (a true function template) is only necessary when cross-type comparisons are needed. - -### The Constraining Effect of Barton-Nackman - -The true power of the traditional Barton-Nackman Trick lies in **constraint**: by defining the operator as a friend of the class, the operator only participates in overload resolution when the operand types match the class template. - -```cpp -template -class Point { - T x, y; -public: - // 这个operator==只对Point及其派生类可见 - friend bool operator==(const Point& a, const Point& b) { - return a.x == b.x && a.y == b.y; - } -}; - -// 全局的一个通用operator== -template -bool operator==(const T& a, const U& b) { - return false; -} - -Point p{1, 2}; -int x = 5; - -p == p; // 调用Point的友元operator== -x == p; // 调用通用operator==(Point的友元不匹配) -``` - -### Simplified Modern Approach - -In modern C++ (C++11 and later), the core value of the Barton-Nackman Trick has diminished because we have better techniques (such as ``std::enable_if``, C++20 Concepts). However, the friend injection syntax remains concise and practical: - -```cpp -template -class Point { - T x, y; -public: - Point(T x, T y) : x(x), y(y) {} - - // 现代推荐写法:简洁的友元注入 - friend bool operator==(const Point& a, const Point& b) { - return a.x == b.x && a.y == b.y; - } - - friend bool operator!=(const Point& a, const Point& b) { - return !(a == b); - } - - // 其他比较运算符... - friend auto operator<=>(const Point& a, const Point& b) { - if (auto cmp = a.x <=> b.x; cmp != 0) return cmp; - return a.y <=> b.y; - } -}; -``` - -**Note**: The C++20 three-way comparison operator ``operator<=>`` automatically generates all other comparison operators, so defining it alone is sufficient. - ------- - -## Relationship Between Barton-Nackman and CRTP - -The Barton-Nackman Trick is the predecessor to CRTP. Understanding the relationship between the two helps master template metaprogramming at a deeper level. - -### CRTP: Curiously Recurring Template Pattern - -CRTP is a design pattern where a derived class passes itself as a template argument to its base class: - -```cpp -template -class Base { -public: - void interface() { - // 编译期将基类指针转换为派生类指针 - static_cast(this)->implementation(); - } -}; - -class Derived : public Base { -public: - void implementation() { - // 具体实现 - } -}; -``` - -### Evolution from Barton-Nackman to CRTP - -Early Barton-Nackman Trick code looked like this: - -```cpp -// Barton-Nackman 原始风格(简化版) -template -class Ordered { -public: - friend bool operator<(const T& a, const T& b) { - return a.less(b); - } - friend bool operator>(const T& a, const T& b) { - return b < a; - } - // ...其他运算符 -}; - -class Point : public Ordered { - double x, y; -public: - bool less(const Point& other) const { - if (x != other.x) return x < other.x; - return y < other.y; - } -}; -``` - -Notice that ``Point`` inherits from ``Ordered`` — this is the core of CRTP! - -### Modern Implementation (Using CRTP) - -```cpp -template -class Comparable { -public: - friend bool operator<(const Derived& a, const Derived& b) { - return a.compare(b) < 0; - } - - friend bool operator>(const Derived& a, const Derived& b) { - return b < a; - } - - friend bool operator<=(const Derived& a, const Derived& b) { - return !(a > b); - } - - friend bool operator>=(const Derived& a, const Derived& b) { - return !(a < b); - } - - friend bool operator==(const Derived& a, const Derived& b) { - return a.compare(b) == 0; - } - - friend bool operator!=(const Derived& a, const Derived& b) { - return !(a == b); - } -}; - -template -class Point : public Comparable> { - T x, y; -public: - Point(T x, T y) : x(x), y(y) {} - - int compare(const Point& other) const { - if (x < other.x) return -1; - if (x > other.x) return 1; - if (y < other.y) return -1; - if (y > other.y) return 1; - return 0; - } -}; -``` - -### Comparison of the Two Patterns - -| Characteristic | Barton-Nackman Trick | CRTP | -|------|---------------------|------| -| Era | 1994 | Late 1990s | -| Core | Friend injection | Inheritance + templates | -| Operator location | Friend functions defined inside the class | Friend functions defined in the base class | -| Code reuse | Repeated definition in each class | Unified implementation in the base class | -| Flexibility | Lower | Higher | -| Modern applicability | Sufficient for simple scenarios | Recommended for complex hierarchies | - -**Selection recommendations**: - -- **Simple classes**: Use friend injection directly; no need for CRTP -- **Need to share extensive operator logic**: Use a CRTP base class -- **C++20**: Consider using Concepts-constrained operators - ------- - -## Template Techniques for Operator Overloading - -Let's explore several common template techniques for operator overloading. - -### Technique 1: Friend Functions vs Member Functions - -```cpp -template -class Point { - T x, y; -public: - - // ❌ 成员函数:不对称,需要 Point == 其他类型 能工作 - bool operator==(const Point& other) const { - return x == other.x && y == other.y; - } - - // ✅ 友元函数:对称,两边都能处理隐式转换 - friend bool operator==(const Point& a, const Point& b) { - return a.x == b.x && a.y == b.y; - } -}; -``` - -**Best practices**: - -- **Assignment, subscript, call, arrow**: Must be member functions -- **Compound assignment (+=, -=, etc.)**: Usually member functions -- **Arithmetic, comparison, I/O**: Usually non-member (friend) functions -- **Type conversion**: Must be member functions - -### Technique 2: Cross-Type Comparison - -Using template friends to implement comparisons between different types: - -```cpp -template -class Point { - T x, y; -public: - Point(T x, T y) : x(x), y(y) {} - - // 同类型比较 - friend bool operator==(const Point& a, const Point& b) { - return a.x == b.x && a.y == b.y; - } - - // 跨类型比较(int 和 double 可以比较) - template - friend bool operator==(const Point& a, const Point& b) { - return a.x == b.x && a.y == b.y; - } -}; - -// 使用 -Point pi{1, 2}; -Point pd{1.0, 2.0}; -bool eq = (pi == pd); // ✅ 跨类型比较 -``` - -### Technique 3: Using std::common_type to Unify Return Types - -```cpp -#include - -template -auto add(const T& a, const U& b) -> std::common_type_t { - return a + b; -} - -// 对于运算符 -template -class Point { - T x, y; -public: - template - auto operator+(const Point& other) const - -> Point> { - return {x + other.x, y + other.y}; - } -}; - -Point pi{1, 2}; -Point pd{3.5, 4.5}; -auto result = pi + pd; // Point{4.5, 6.5} -``` - -### Technique 4: C++20 Three-Way Comparison Operator - -C++20 greatly simplifies the definition of comparison operators: - -```cpp -template -class Point { - T x, y; -public: - Point(T x, T y) : x(x), y(y) {} - - // 只需要定义一个运算符! - friend auto operator<=>(const Point&, const Point&) = default; -}; - -// 编译器自动生成: -// ==, !=, <, <=, >, >= -``` - -Custom three-way comparison: - -```cpp -template -class Point { - T x, y; -public: - Point(T x, T y) : x(x), y(y) {} - - friend auto operator<=>(const Point& a, const Point& b) { - if (auto cmp = a.x <=> b.x; cmp != 0) return cmp; - return a.y <=> b.y; - } - - // 三路比较不会自动生成==,需要单独定义 - friend bool operator==(const Point& a, const Point& b) { - return a.x == b.x && a.y == b.y; - } -}; -``` - -### Technique 5: Constrained Operators (C++20 Concepts) - -```cpp -template -concept Numeric = std::integral || std::floating_point; - -template -class Point { - T x, y; -public: - Point(T x, T y) : x(x), y(y) {} - - // 只有满足 Numeric 的类型才能比较 - friend auto operator<=>(const Point&, const Point&) = default; -}; - -Point pi; // ✅ -Point ps; // ❌ 编译错误 -``` - ------- - -## Hands-On: Implementing a Comparable Point - -Now let's implement a complete, comparable ``Point`` type, combining the techniques learned in this chapter. - -### Requirements Definition - -Our ``Point`` should: - -1. Support arbitrary numeric types (int, float, double, etc.) -2. Support all comparison operators (==, !=, <, <=, >, >=) -3. Support arithmetic operators (+, -, *, /) -4. Support the stream output operator (<<) -5. Use friend injection for implementation -6. Provide type-safe distance calculation - -### Complete Implementation - -```cpp -#include -#include -#include -#include - -template -concept Numeric = std::integral || std::floating_point; - -template -class Point { - T x_, y_; - -public: - // 构造函数 - constexpr Point() : x_(0), y_(0) {} - - constexpr Point(T x, T y) : x_(x), y_(y) {} - - // Getter - constexpr T x() const { return x_; } - constexpr T y() const { return y_; } - - // Setter - constexpr void set_x(T x) { x_ = x; } - constexpr void set_y(T y) { y_ = y; } - - // ===== 比较运算符 ===== - - // C++20 三路比较(自动生成所有比较运算符) - constexpr friend auto operator<=>(const Point& a, const Point& b) { - if (auto cmp = a.x_ <=> b.x_; cmp != 0) return cmp; - return a.y_ <=> b.y_; - } - - constexpr friend bool operator==(const Point& a, const Point& b) { - return a.x_ == b.x_ && a.y_ == b.y_; - } - - // ===== 算术运算符 ===== - - constexpr friend Point operator+(const Point& a, const Point& b) { - return {a.x_ + b.x_, a.y_ + b.y_}; - } - - constexpr friend Point operator-(const Point& a, const Point& b) { - return {a.x_ - b.x_, a.y_ - b.y_}; - } - - constexpr friend Point operator*(const Point& p, T scalar) { - return {p.x_ * scalar, p.y_ * scalar}; - } - - constexpr friend Point operator*(T scalar, const Point& p) { - return p * scalar; - } - - constexpr friend Point operator/(const Point& p, T scalar) { - return {p.x_ / scalar, p.y_ / scalar}; - } - - // ===== 复合赋值运算符 ===== - - constexpr Point& operator+=(const Point& other) { - x_ += other.x_; - y_ += other.y_; - return *this; - } - - constexpr Point& operator-=(const Point& other) { - x_ -= other.x_; - y_ -= other.y_; - return *this; - } - - constexpr Point& operator*=(T scalar) { - x_ *= scalar; - y_ *= scalar; - return *this; - } - - constexpr Point& operator/=(T scalar) { - x_ /= scalar; - y_ /= scalar; - return *this; - } - - // ===== 流输出运算符 ===== - - friend std::ostream& operator<<(std::ostream& os, const Point& p) { - return os << '(' << p.x_ << ", " << p.y_ << ')'; - } - - // ===== 实用方法 ===== - - // 计算到原点的距离 - [[nodiscard]] constexpr double distance_from_origin() const { - return std::hypot(static_cast(x_), static_cast(y_)); - } - - // 计算到另一个点的距离 - [[nodiscard]] constexpr double distance_to(const Point& other) const { - double dx = static_cast(x_ - other.x_); - double dy = static_cast(y_ - other.y_); - return std::hypot(dx, dy); - } - - // 点积 - [[nodiscard]] constexpr T dot(const Point& other) const { - return x_ * other.x_ + y_ * other.y_; - } - - // 叉积(2D中返回标量) - [[nodiscard]] constexpr T cross(const Point& other) const { - return x_ * other.y_ - y_ * other.x_; - } - - // 判断是否为零点 - [[nodiscard]] constexpr bool is_zero() const { - return x_ == T{} && y_ == T{}; - } -}; - -// ===== 跨类型算术运算 ===== - -template -auto operator+(const Point& a, const Point& b) { - using Common = std::common_type_t; - return Point{ - static_cast(a.x()) + static_cast(b.x()), - static_cast(a.y()) + static_cast(b.y()) - }; -} - -template -auto operator-(const Point& a, const Point& b) { - using Common = std::common_type_t; - return Point{ - static_cast(a.x()) - static_cast(b.x()), - static_cast(a.y()) - static_cast(b.y()) - }; -} -``` - -### Usage Examples - -```cpp -#include -#include - -int main() { - // 基本构造 - Point p1{3, 4}; - Point p2{1, 2}; - - // 比较运算符 - assert(p1 == p1); - assert(p1 != p2); - assert(p1 > p2); // 按字典序比较 - - // 算术运算 - auto p3 = p1 + p2; // Point{4, 6} - auto p4 = p1 - p2; // Point{2, 2} - auto p5 = p1 * 2; // Point{6, 8} - auto p6 = p1 / 2; // Point{1, 2} - - // 复合赋值 - Point p7{5, 5}; - p7 += p2; // p7 变成 {6, 7} - - // 跨类型运算 - Point pi{10, 20}; - Point pd{1.5, 2.5}; - auto mixed = pi + pd; // Point{11.5, 22.5} - - // 输出 - std::cout << "p1 = " << p1 << '\n'; // p1 = (3, 4) - std::cout << "mixed = " << mixed << '\n'; // mixed = (11.5, 22.5) - - // 实用方法 - Point origin{0, 0}; - Point p{3, 4}; - std::cout << "Distance: " << p.distance_from_origin() << '\n'; // 5.0 - std::cout << "Dot product: " << p.dot(Point{1, 0}) << '\n'; // 3.0 - - return 0; -} -``` - -### Embedded-Optimized Version - -For embedded environments, we might need a lighter-weight implementation: - -```cpp -#include - -template -class EmbeddedPoint { - T x_, y_; - -public: - constexpr EmbeddedPoint() : x_(0), y_(0) {} - constexpr EmbeddedPoint(T x, T y) : x_(x), y_(y) {} - - // 简化的比较(只实现 == 和 <) - constexpr friend bool operator==(const EmbeddedPoint& a, const EmbeddedPoint& b) { - return a.x_ == b.x_ && a.y_ == b.y_; - } - - constexpr friend bool operator<(const EmbeddedPoint& a, const EmbeddedPoint& b) { - return (a.x_ < b.x_) || (a.x_ == b.x_ && a.y_ < b.y_); - } - - // 内联算术运算 - constexpr EmbeddedPoint operator+(const EmbeddedPoint& other) const { - return {static_cast(x_ + other.x_), static_cast(y_ + other.y_)}; - } - - // 饱和加法(避免溢出) - constexpr EmbeddedPoint saturated_add(const EmbeddedPoint& other) const { - if constexpr (std::is_unsigned_v) { - T new_x = (x_ > std::numeric_limits::max() - other.x_) - ? std::numeric_limits::max() - : x_ + other.x_; - T new_y = (y_ > std::numeric_limits::max() - other.y_) - ? std::numeric_limits::max() - : y_ + other.y_; - return {new_x, new_y}; - } else { - return *this + other; // 有符号类型暂不支持 - } - } - - // 快速距离平方(避免浮点运算) - constexpr T distance_squared() const { - return x_ * x_ + y_ * y_; - } - - // 判断点是否在矩形内 - constexpr bool is_inside(T left, T top, T right, T bottom) const { - return x_ >= left && x_ <= right && y_ >= top && y_ <= bottom; - } -}; - -// 使用场景:图形界面、触摸屏检测 -using ScreenPoint = EmbeddedPoint; - -// 检测触摸点是否在按钮区域内 -constexpr bool is_touch_in_button(ScreenPoint touch, int16_t btn_x, - int16_t btn_y, int16_t btn_w, int16_t btn_h) { - return touch.is_inside(btn_x, btn_y, btn_x + btn_w, btn_y + btn_h); -} -``` - -### CRTP Comparable Base Class Version - -If we have multiple classes that need comparison capabilities, we can use a CRTP base class: - -```cpp -template -class Comparable { -public: - // 三路比较 - friend auto operator<=>(const Comparable&, const Comparable&) = default; - - // 相等比较 - friend bool operator==(const Comparable& a, const Comparable& b) { - return static_cast(a).compare_impl( - static_cast(b) - ) == 0; - } - -protected: - ~Comparable() = default; -}; - -template -class Point : public Comparable, T> { - T x_, y_; - -public: - Point(T x, T y) : x_(x), y_(y) {} - - int compare_impl(const Point& other) const { - if (x_ < other.x_) return -1; - if (x_ > other.x_) return 1; - if (y_ < other.y_) return -1; - if (y_ > other.y_) return 1; - return 0; - } - - T x() const { return x_; } - T y() const { return y_; } -}; - -// 其他类也可以复用 -template -class Vector3D : public Comparable, T> { - T x_, y_, z_; - -public: - Vector3D(T x, T y, T z) : x_(x), y_(y), z_(z) {} - - int compare_impl(const Vector3D& other) const { - if (auto cmp = x_ <=> other.x_; cmp != 0) return cmp < 0 ? -1 : 1; - if (auto cmp = y_ <=> other.y_; cmp != 0) return cmp < 0 ? -1 : 1; - if (auto cmp = z_ <=> other.z_; cmp != 0) return cmp < 0 ? -1 : 1; - return 0; - } -}; -``` - ------- - -## Embedded Application Scenarios - -### Scenario 1: Sensor Data Comparison - -```cpp -template -class SensorReading { - T value_; - uint32_t timestamp_; - -public: - SensorReading(T value, uint32_t timestamp) - : value_(value), timestamp_(timestamp) {} - - // 按值比较(用于阈值检测) - friend bool operator==(const SensorReading& a, const SensorReading& b) { - return a.value_ == b.value_; - } - - friend auto operator<=>(const SensorReading& a, const SensorReading& b) { - return a.value_ <=> b.value_; - } - - // 按时间戳比较(用于排序) - friend bool chronological_order(const SensorReading& a, - const SensorReading& b) { - return a.timestamp_ < b.timestamp_; - } - - T value() const { return value_; } - uint32_t timestamp() const { return timestamp_; } -}; - -// 使用 -SensorReading temp1{25, 1000}; -SensorReading temp2{30, 1005}; - -if (temp2 > temp1) { - // 温度升高 -} -``` - -### Scenario 2: Register Address Comparison - -```cpp -template -class Register { - AddrType address_; - DataType value_; - -public: - constexpr Register(AddrType addr, DataType val) - : address_(addr), value_(val) {} - - // 按地址比较(用于查找) - friend bool operator==(const Register& a, const Register& b) { - return a.address_ == b.address_; - } - - friend auto operator<=>(const Register& a, const Register& b) { - return a.address_ <=> b.address_; - } - - AddrType address() const { return address_; } - DataType value() const { return value_; } -}; - -// 使用 -using GPIOReg = Register; - -constexpr GPIOReg gpio_a{0x40020000, 0}; -constexpr GPIOReg gpio_b{0x40020400, 0}; - -if (gpio_a < gpio_b) { - // gpio_a 的地址更小 -} -``` - -### Scenario 3: Configuration Parameter Validation - -```cpp -template -class ConfigParameter { - const char* name_; - T value_; - T min_; - T max_; - -public: - constexpr ConfigParameter(const char* name, T val, T min_val, T max_val) - : name_(name), value_(val), min_(min_val), max_(max_val) { - // 编译期验证 - static_assert(min_val <= max_val, "Invalid range"); - } - - // 按名称比较 - friend bool operator==(const ConfigParameter& a, const ConfigParameter& b) { - return std::strcmp(a.name_, b.name_) == 0; - } - - // 按值比较 - friend bool operator<(const ConfigParameter& a, const ConfigParameter& b) { - return a.value_ < b.value_; - } - - constexpr bool is_valid() const { - return value_ >= min_ && value_ <= max_; - } - - const char* name() const { return name_; } - T value() const { return value_; } -}; -``` - -### Scenario 4: Communication Protocol Packet Comparison - -```cpp -template -class Packet { - SeqType sequence_; - PayloadSize size_; - uint8_t data_[256]; - -public: - Packet(SeqType seq, PayloadSize sz) : sequence_(seq), size_(sz) {} - - // 按序列号比较 - friend auto operator<=>(const Packet& a, const Packet& b) { - return a.sequence_ <=> b.sequence_; - } - - friend bool operator==(const Packet& a, const Packet& b) { - return a.sequence_ == b.sequence_; - } - - SeqType sequence() const { return sequence_; } - PayloadSize size() const { return size_; } -}; -``` - ------- - -## Common Pitfalls and Solutions - -### Pitfall 1: Friend Functions Not in the Global Scope - -```cpp -template -class Point { - friend bool operator==(const Point& a, const Point& b) { - return a.x == b.x && a.y == b.y; - } -}; - -Point p1, p2; -// operator==(p1, p2); // ❌ 可能找不到(取决于编译器) -p1 == p2; // ✅ 通过ADL找到 -``` - -**Solution**: Always use the ``p1 == p2`` form; do not call ``operator==`` directly. - -### Pitfall 2: Template Argument Deduction Failure - -```cpp -template -class Point { - template - friend Point operator+(const Point& a, const Point& b); -}; - -template -Point operator+(const Point& a, const Point& b) { - return {a.x + b.x, a.y + b.y}; // ❌ 无法访问私有成员 -} -``` - -**Solution**: Define the friend function inside the class, or use public accessors. - -### Pitfall 3: Infinite Recursion in CRTP - -```cpp -template -class Base { -public: - void foo() { - static_cast(this)->foo(); // ❌ 无限递归! - } -}; - -class Derived : public Base { -public: - void foo() { - // 这里会调用 Base::foo,形成无限循环 - } -}; -``` - -**Solution**: Ensure ``Derived::foo`` and ``Base::foo`` have different names, or use ``this->foo()`` instead of calling after a cast. - -### Pitfall 4: Returning a Reference to a Local Variable - -```cpp -template -class Point { - friend const Point& operator+(const Point& a, const Point& b) { - Point result{a.x + b.x, a.y + b.y}; // ❌ 局部变量 - return result; // ❌ 返回局部变量的引用! - } -}; -``` - -**Solution**: Return by value instead of by reference: - -```cpp -friend Point operator+(const Point& a, const Point& b) { - return {a.x + b.x, a.y + b.y}; // ✅ 返回值(可能被RVO优化) -} -``` - -### Pitfall 5: Default Implementation of C++20 Three-Way Comparison - -```cpp -template -class Point { - T x, y; -public: - // 默认的 operator<=> 会逐成员比较 - friend auto operator<=>(const Point&, const Point&) = default; -}; - -Point p1, p2; -// p1 == p2; // ❌ 指针比较,不是值比较! -``` - -**Solution**: Customize the comparison for pointer types, or disable instantiation for pointer types. - -```cpp -template -class Point { // 使用 Concept 约束 - T x, y; -public: - friend auto operator<=>(const Point&, const Point&) = default; -}; -``` - -### Pitfall 6: Template Argument Deduction for Friend Functions - -```cpp -template -class Point { - template - friend bool operator==(const Point&, const Point&); - // ⚠️ 这会让 Point 和 Point 也能比较 - // 但可能不是你想要的! -}; -``` - -**Solution**: Use ``std::same_as`` constraints or use a non-template friend. - -```cpp -// 方案1:非模板友元(推荐) -template -class Point { - friend bool operator==(const Point& a, const Point& b) { - return a.x == b.x && a.y == b.y; - } -}; - -// 方案2:C++20 约束 -template -class Point { - template - friend bool operator==(const Point& a, const Point& b) - requires std::same_as { - return a.x == b.x && a.y == b.y; - } -}; -``` - ------- - -## C++20 Feature: The Spaceship Operator - -The C++20 three-way comparison operator (``operator<=>``, commonly known as the spaceship operator) has completely changed how we write comparison operators. - -### Default Generation - -```cpp -template -class Point { - T x, y; -public: - // 一行代码,自动生成 ==、!=、<、<=、>、>= - friend auto operator<=>(const Point&, const Point&) = default; -}; -``` - -### Custom Implementation - -```cpp -template -class Point { - T x, y; -public: - friend auto operator<=>(const Point& a, const Point& b) { - if (auto cmp = a.x <=> b.x; cmp != 0) return cmp; - return a.y <=> b.y; - } - - // 三路比较不会自动生成 ==,需要单独定义 - friend bool operator==(const Point& a, const Point& b) { - return a.x == b.x && a.y == b.y; - } -}; -``` - -### Comparison Categories - -``operator<=>`` returns different comparison categories: - -| Return Type | Description | Example Types | -|---------|------|----------| -| ``std::strong_ordering`` | Totally substitutable | ``int``, ``std::string`` | -| ``std::weak_ordering`` | Equivalent but substitutable | ``float`` (NaN) | -| ``std::partial_ordering`` | Partially comparable | Complex numbers (only equality is meaningful) | - -```cpp -#include - -template -class Point { - T x, y; -public: - // 指定返回类型 - friend std::strong_ordering operator<=>(const Point& a, const Point& b) { - if (auto cmp = a.x <=> b.x; cmp != 0) return cmp; - return a.y <=> b.y; - } -}; -``` - -### Same-Type and Cross-Type Comparisons - -C++20 supports defining operators separately for same-type and cross-type comparisons: - -```cpp -template -class Point { - T x, y; -public: - // 同类比较 - friend auto operator<=>(const Point&, const Point&) = default; - - // 异类比较(用默认的 rewritable 方式) - template - friend auto operator<=>(const Point& a, const Point& b) { - if (auto cmp = a.x <=> b.x; cmp != 0) return cmp; - return a.y <=> b.y; - } -}; -``` - ------- - -## Performance Considerations - -### Compile-Time Overhead - -The compile-time overhead of friend injection and the Barton-Nackman Trick mainly comes from: - -1. **Template instantiation**: Each type combination generates new code -2. **Symbol table bloat**: A large number of friend functions increases the symbol table -3. **ADL lookup**: The compiler needs to perform additional ADL lookups - -### Runtime Overhead - -When used correctly, there is **zero runtime overhead**: - -```cpp -// 编译前 -Point p1{1, 2}, p2{3, 4}; -bool result = (p1 == p2); - -// 编译后(近似) -bool result = (p1.x == p2.x && p1.y == p2.y); -// 完全内联,无函数调用 -``` - -### Optimization Recommendations - -1. **For small classes**: Use friend injection for concise code -2. **For large hierarchies**: Use a CRTP base class to reuse code -3. **For C++20**: Prefer the ``operator<=>`` default implementation -4. **Limit instantiation**: Use Concepts to constrain template parameters - -```cpp -// ✅ 好:使用 Concepts 限制 -template -class Point { /* ... */ }; - -// ❌ 不好:对任何类型都实例化 -template -class Point { /* ... */ }; -``` - -1. **Use ``constexpr``**: Encourage compile-time computation - -```cpp -constexpr Point p1{1, 2}; -constexpr Point p2{3, 4}; -constexpr bool eq = (p1 == p2); // 编译期计算 -static_assert(!eq); -``` - ------- - -## Summary - -In this chapter, we explored template friends and the Barton-Nackman Trick in depth: - -### Core Concepts - -| Concept | Purpose | Use Case | -|------|------|----------| -| Friend injection | Define friend functions inside a class, callable externally via ADL | Simplify operator overloading | -| Barton-Nackman Trick | Constrain operators to be available only for specific types | Early constrained generic programming | -| CRTP | Derived class serves as the template argument of the base class | Share base class logic | -| Three-way comparison | C++20 unified comparison operator | Simplify comparison operator definitions | - -### Practical Takeaways - -1. **Operator overloading choices**: - - ``==``, ``!=``, ``<``, ``<=``, ``>``, ``>=``: Friend functions - - ``+=``, ``-=``, ``*=``, ``/=``: Member functions - - C++20: Use ``operator<=>`` - -2. **Embedded optimizations**: - - Use ``constexpr`` for compile-time computation - - Avoid floating-point operations; use integer distance squared - - Use Concepts constraints to reduce instantiation - -3. **Common pitfalls**: - - Friend functions can only be found via ADL - - Return by value, not by reference - - Avoid infinite recursion in CRTP - -4. **Modern C++ recommendations**: - - Simple scenarios: Direct friend injection - - Complex scenarios: CRTP base class - - C++20: ``operator<=>`` default implementation + Concepts - -**In the next chapter**, we will explore **advanced template metaprogramming**, learning advanced techniques like SFINAE, type traits, and tag dispatch, and implementing a compile-time reflection system. diff --git a/documents/en/vol4-advanced/07-template-aliases-and-using.md b/documents/en/vol4-advanced/07-template-aliases-and-using.md deleted file mode 100644 index 8d9309da9..000000000 --- a/documents/en/vol4-advanced/07-template-aliases-and-using.md +++ /dev/null @@ -1,1316 +0,0 @@ ---- -chapter: 12 -cpp_standard: -- 11 -- 14 -- 17 -- 20 -description: Master the powerful features of modern C++ type aliases and the implementation - principles of the standard library -difficulty: intermediate -order: 7 -platform: host -prerequisites: -- 'Chapter 12: 类模板详解' -- 'Chapter 12: 模板元编程基础' -reading_time_minutes: 35 -tags: -- cpp-modern -- host -- intermediate -title: Template aliases and using declarations ---- -# Modern C++ for Embedded Systems Tutorial — Template Aliases and Using Declarations - -Have you ever been tortured by code like this? - -```cpp -std::map>::iterator it = sensors.begin(); - -// 或者更可怕的 -typename std::enable_if::value, T>::type result = process(value); -``` - -This kind of code is not only hard to read, but also error-prone. The `using` declaration and alias templates introduced in C++11 are exactly the right tools to solve this problem. In this chapter, we will dive into the modern syntax for type aliases, trace the design principles of `std::enable_if_t` and `std::conditional_t` in the standard library, and learn how to use alias templates to simplify complex template type declarations. - ------- - -## From typedef to using - -### The History of typedef - -Before C++11, we could only use `typedef` to define type aliases: - -```cpp -// 基本用法 -typedef unsigned int uint32_t; -typedef int int_array[10]; -typedef void (*function_ptr)(int); - -// 复杂一点的 -typedef std::map> StringToIntVectorMap; -``` - -The problems with `typedef` are obvious: - -1. **Inconsistent syntax**: The syntax of `typedef` declarations is similar to variable declarations, but the keyword placement is awkward. -2. **No template support**: It is impossible to define aliases for template types. -3. **Poor readability**: Complex type aliases are difficult to parse. - -```cpp -// 这是什么? -typedef void (*SignalHandler)(int, siginfo_t*, void*); - -// 需要从右向左读:SignalHandler是一个函数指针类型, -// 指向的函数接受(int, siginfo_t*, void*)参数,返回void -``` - -### The Elegant Arrival of using - -The `using` declaration introduced in C++11 provides a clearer syntax: - -```cpp -// 基本用法 -using uint32_t = unsigned int; -using int_array = int[10]; -using function_ptr = void(*)(int); - -// 语法更自然:从左向右读 -using SignalHandler = void(*)(int, siginfo_t*, void*); -``` - -**Key advantage**: The "alias = original type" structure of the `using` syntax is more intuitive and consistent with variable initialization. - -### typedef vs using Comparison Table - -| Feature | typedef | using | -|---------|---------|-------| -| Basic type alias | Supported | Supported | -| Function pointer alias | Supported | Supported (clearer) | -| Template alias | Not supported | Supported | -| Template template parameter | Difficult | Intuitive | -| Scope | Same | Same | -| Readability | Poor | Better | - -### Function Pointer Alias Comparison - -```cpp -// typedef 写法 -typedef void (*OldStyleCallback)(int error_code, const char* message); -OldStyleCallback cb1 = nullptr; - -// using 写法 -using NewStyleCallback = void(*)(int error_code, const char* message); -NewStyleCallback cb2 = nullptr; - -// 多个参数的函数指针 -typedef void (*ComplexOld)(int, double, const std::string&); -using ComplexNew = void(*)(int, double, const std::string&); - -// 返回函数指针的函数 -// typedef 版本(非常晦涩) -typedef void (*FuncPtr)(int); -FuncPtr getFunc typedef_Old(int id); - -// using 版本(相对清晰) -using FuncPtr = void(*)(int); -FuncPtr getFunc_New(int id); - -// 返回函数指针的函数指针...你能看出区别吗? -using CallbackFactory = auto(*)(int) -> void(*)(int); -``` - ------- - -## Alias Templates - -### The Fatal Flaw of typedef - -The biggest problem with `typedef` is that **it cannot define aliases for templates**: - -```cpp -template -struct MyAllocator { - using value_type = T; - T* allocate(std::size_t n); - void deallocate(T* p, std::size_t n); -}; - -// ❌ typedef 无法做到 -// typedef MyAllocator MyAlloc; // 错误:T 未定义 - -// 只能为具体类型别名 -typedef MyAllocator MyIntAllocator; // 只适用于 int -``` - -This leads to a lot of repetitive code, requiring a new declaration every time a different type is used. - -### using Alias Templates - -C++11's `using` perfectly solves this problem: - -```cpp -// ✅ 别名模板 -template -using MyAlloc = MyAllocator; - -// 使用 -MyAlloc int_alloc; -MyAlloc double_alloc; -MyAlloc string_alloc; -``` - -**Key point**: An alias template **is not a definition of a new type**, but syntactic sugar for an existing type. During template instantiation, it is directly replaced by the original type. - -### Practical Application: Simplifying Standard Library Containers - -```cpp -// 嵌入式常用配置 -template -using FixedVector = std::vector>; - -// 使用 -FixedVector rx_buffer; // 32字节接收缓冲区 -FixedVector adc_samples; // 16个ADC采样 - -// 更复杂的例子 -template -using StringMap = std::map, PoolAllocator<64>>; - -StringMap settings; -``` - -### Alias Templates vs Specialization - -Alias templates **cannot be specialized**, which is an important distinction from class templates: - -```cpp -// 别名模板(不能特化) -template -using Ptr = T*; - -// ❌ 错误:别名模板不能特化 -// template<> -// using Ptr = void*; - -// 如果需要特化,使用类模板 -template -struct PtrTraits { - using type = T*; -}; - -// 可以特化 -template<> -struct PtrTraits { - using type = void*; -}; - -// 使用 -template -using SafePtr = typename PtrTraits::type; -``` - -### Embedded Scenario: Peripheral Type Aliases - -```cpp -// HAL 层类型定义 -template -struct Register { - static constexpr RegType address = Address; - using value_type = RegType; - - RegType read() const; - void write(RegType value); -}; - -// 为常用寄存器创建别名 -using GPIOA_MODER = Register; -using GPIOA_ODR = Register; -using SPI1_DR = Register; - -// 使用 -void init_gpio() { - GPIOA_MODER moder; - moder.write(0xABCDFFFF); -} -``` - ------- - -## Tracing the Standard Library: The Origin of the _t Suffix - -You have definitely seen many type aliases with the `_t` suffix: - -```cpp -std::size_t -std::int32_t -std::make_signed_t -std::enable_if_t -std::conditional_t -``` - -These are actually a **naming convention for alias templates** — `_t` stands for "type". Let's trace their design principles. - -### The Evolution of std::enable_if_t - -#### C++11/14 Original Version - -```cpp -// 标准库实现 -template -struct enable_if { - // 空:当 Condition 为 false 时 -}; - -template -struct enable_if { - using type = T; // 只有 Condition 为 true 时才有 type 成员 -}; - -// 使用方式(冗长) -template -typename std::enable_if::value, T>::type -process(T value) { - return value * 2; -} -``` - -#### C++14 Alias Template Simplification - -```cpp -// C++14 添加的别名模板 -template -using enable_if_t = typename enable_if::type; - -// 使用方式(简洁!) -template -std::enable_if_t, T> -process(T value) { - return value * 2; -} -``` - -#### Implementation Principle Analysis - -```cpp -// 逐步剖析 -template -using enable_if_t = typename enable_if::type; -// ^^^^^^^^^^^^^^^^^^^^^^^^^^ -// 这部分引用原始 struct 的 type 成员 - -// 当 B = true 时 -// enable_if::type = int -// 所以 enable_if_t = int - -// 当 B = false 时 -// enable_if 没有 type 成员 -// 所以 enable_if_t 替换失败,触发 SFINAE -``` - -### The Design of std::conditional_t - -#### C++11/14 Original Version - -```cpp -// 标准库实现 -template -struct conditional { - using type = TrueType; // Condition 为 true 时的默认情况 -}; - -template -struct conditional { - using type = FalseType; // Condition 为 false 时的特化 -}; - -// 使用方式(冗长) -template -void process(typename std::conditional< - std::is_integral::value, - std::integral_constant, - std::integral_constant ->::type tag); -``` - -#### C++14 Alias Template Simplification - -```cpp -// C++14 添加的别名模板 -template -using conditional_t = typename conditional::type; - -// 使用方式(简洁!) -template -void process(std::conditional_t< - std::is_integral_v, - std::integral_constant, - std::integral_constant -> tag); -``` - -#### Practical Application Example - -```cpp -// 根据类型大小选择最优算法 -template -using FastCopy = std::conditional_t< - sizeof(T) == 1, // 如果是 1 字节 - void(*)(uint8_t*, const uint8_t*, std::size_t), // 使用 memcpy - void(*)(T*, const T*, std::size_t) // 否则使用循环 ->; - -// 嵌入式场景:根据架构选择指令 -template -using OptimalAdd = std::conditional_t< - std::is_integral_v && sizeof(T) <= 4, - std::integral_constant, // 使用单周期加法 - std::integral_constant // 需要多周期或库调用 ->; -``` - -### Common _t Alias Templates - -| Alias Template | Description | Example | -|---------|------|------| -| `std::enable_if_t` | Conditionally enable a type | `std::enable_if_t` | -| `std::conditional_t` | Conditionally select a type | `std::conditional_t` | -| `std::remove_reference_t` | Remove a reference | `std::remove_reference_t` = `int` | -| `std::add_const_t` | Add const | `std::add_const_t` = `const int` | -| `std::make_signed_t` | Convert to signed | `std::make_signed_t` = `int` | -| `std::make_unsigned_t` | Convert to unsigned | `std::make_unsigned_t` = `unsigned int` | -| `std::common_type_t` | Common type | `std::common_type_t` = `float` | -| `std::decay_t` | Decayed type | `std::decay_t` = `int` | -| `std::invoke_result_t` | Invocation result type | `std::invoke_result_t` | - ------- - -## In Practice: Simplifying Complex Template Type Declarations - -Let's demonstrate the power of alias templates through a few real-world scenarios. - -### Scenario 1: Type-Safe Communication Protocols - -```cpp -// 协议基础类型 -template -struct BigEndian { - T value; - BigEndian() = default; - explicit BigEndian(T v) : value(htobe(v)) {} - T get() const { return betoh(value); } -}; - -// 为常用大小创建别名 -using BE16 = BigEndian; -using BE32 = BigEndian; -using BE64 = BigEndian; - -// 协议头部 -struct PacketHeader { - BE16 sync; // 同步字 - BE16 length; // 长度 - BE32 timestamp; // 时间戳 - BE16 checksum; // 校验和 -}; - -// 解析函数 -void parse_packet(const uint8_t* buffer) { - auto header = reinterpret_cast(buffer); - uint16_t len = header->length.get(); // 自动转换字节序 - // ... -} -``` - -### Scenario 2: Embedded Callback System - -```cpp -// 回调类型定义 -template -using Callback = void(*)(Args...); - -// 具体回调别名 -using ErrorHandler = Callback; -using DataHandler = Callback; -using TimeoutHandler = Callback; - -// 回调管理器 -template -class CallbackManager { -public: - using CallbackType = Callback; - using Token = std::size_t; - - Token register_callback(CallbackType cb) { - Token token = next_token_++; - callbacks_[token] = cb; - return token; - } - - void invoke(Args... args) { - for (auto& [token, cb] : callbacks_) { - if (cb) cb(args...); - } - } - -private: - std::map callbacks_; - Token next_token_ = 0; -}; - -// 使用 -void error_callback(int code, const char* msg) { - log_error("Error %d: %s", code, msg); -} - -// 注册 -CallbackManager error_manager; -auto token = error_manager.register_callback(error_callback); -``` - -### Scenario 3: Memory Pool Configuration - -```cpp -// 内存池特征 -template -struct PoolTraits { - static constexpr std::size_t block_size = BlockSize; - static constexpr std::size_t blocks = Blocks; - static constexpr std::size_t total_size = BlockSize * Blocks; - using block_type = std::aligned_storage_t; -}; - -// 常用配置 -using SmallPool = PoolTraits<32, 64>; // 2KB -using MediumPool = PoolTraits<128, 32>; // 4KB -using LargePool = PoolTraits<512, 16>; // 8KB - -// 内存池实现 -template -class MemoryPool { -public: - using BlockType = typename Traits::block_type; - - void* allocate() { - if (free_list_) { - auto block = free_list_; - free_list_ = *static_cast(block); - return block; - } - return nullptr; - } - - void deallocate(void* ptr) { - auto block = static_cast(ptr); - *block = free_list_; - free_list_ = block; - } - -private: - BlockType storage_[Traits::blocks]; - void* free_list_ = nullptr; -}; - -// 使用 -MemoryPool small_objects; -MemoryPool medium_objects; -``` - -### Scenario 4: Smart Pointer Type Aliases - -```cpp -// 项目统一的智能指针配置 -template -using UniquePtr = std::unique_ptr>; - -template -using SharedPtr = std::shared_ptr; - -// 线程安全的智能指针 -template -using ThreadSafePtr = std::shared_ptr; - -// 单例智能指针 -template -using SingletonPtr = std::unique_ptr; - -// 外设指针(硬件寄存器) -template -using PeripheralPtr = std::uintptr_t; - -// 使用 -class UARTDriver { -public: - static UniquePtr instance() { - return UniquePtr(new UARTDriver()); - } -}; - -auto uart = UARTDriver::instance(); -``` - -### Scenario 5: Sensor Interface Types - -```cpp -// 传感器读取结果 -template -struct SensorResult { - T value; - bool valid; - uint32_t timestamp; -}; - -// 常用传感器结果类型 -using TemperatureResult = SensorResult; -using PressureResult = SensorResult; -using AccelerationResult = SensorResult>; - -// 传感器接口 -template -class ISensor { -public: - using Result = ResultType; - - virtual Result read() = 0; - virtual bool calibrate() = 0; -}; - -// 具体传感器 -class TempSensor : public ISensor { -public: - Result read() override { - Result r; - r.value = read_temperature(); - r.valid = true; - r.timestamp = get_timestamp(); - return r; - } -}; -``` - -### Scenario 6: Type Traits Composition - -```cpp -// 检测类型是否适合作为 DMA 缓冲区 -template -using is_dma_capable = std::conjunction< - std::is_trivially_copyable, - std::has_unique_object_representations, - std::bool_constant<(alignof(T) >= 4)> ->; - -// 获取最佳 DMA 传输类型 -template -using dma_transfer_type = std::conditional_t< - is_dma_capable::value, - T, // 直接传输 - std::aligned_storage_t // 需要拷贝 ->; - -// 使用示例 -template -void dma_transfer(const T* src, T* dst, std::size_t count) { - using TransferType = dma_transfer_type; - - if constexpr (is_dma_capable::value) { - // 直接 DMA 传输 - start_dma(src, dst, count * sizeof(T)); - } else { - // 需要先对齐 - std::array buffer; - // 拷贝到缓冲区,然后 DMA - } -} -``` - ------- - -## Advanced Techniques: Compile-Time Type Selection - -### Technique 1: Recursive Type Construction - -```cpp -// 构建固定大小的整数类型 -template -struct IntOfSize { - static_assert(Bits <= 64, "Type too large"); - using type = std::conditional_t< - Bits <= 8, uint8_t, - std::conditional_t< - Bits <= 16, uint16_t, - std::conditional_t< - Bits <= 32, uint32_t, - uint64_t - > - > - >; -}; - -template -using IntOfSize_t = typename IntOfSize::type; - -// 使用 -IntOfSize_t<12> value = 0xFFF; // 使用 uint16_t -IntOfSize_t<24> data = 0xFFFFFF; // 使用 uint32_t -``` - -### Technique 2: SFINAE-Friendly Type Detection - -```cpp -// 检测类型是否有 serialize 方法 -template -struct has_serialize : std::false_type {}; - -template -struct has_serialize().serialize())>> : std::true_type {}; - -template -using has_serialize_t = typename has_serialize::type; - -template -constexpr bool has_serialize_v = has_serialize::value; - -// 使用 -template -std::enable_if_t, std::string> -to_string(const T& obj) { - return obj.serialize(); -} - -template -std::enable_if_t, std::string> -to_string(const T& obj) { - return std::to_string(obj); -} -``` - -### Technique 3: Policy Selection Aliases - -```cpp -// 根据类型选择最优策略 -template -using OptimizedCopy = std::conditional_t< - std::is_trivially_copyable_v, - std::integral_constant, // memcpy - std::conditional_t< - std::is_pointer_v, - std::integral_constant, // 指针拷贝 - std::integral_constant // 逐元素拷贝 - > ->; - -// 策略实现 -template -void copy_optimized(T* dst, const T* src, std::size_t n) { - constexpr auto strategy = OptimizedCopy::value; - - if constexpr (strategy == 0) { - std::memcpy(dst, src, n * sizeof(T)); - } else if constexpr (strategy == 1) { - for (std::size_t i = 0; i < n; ++i) { - dst[i] = src[i]; - } - } else { - std::copy_n(src, n, dst); - } -} -``` - -### Technique 4: Lazy Type Evaluation - -```cpp -// 延迟计算类型(避免实例化不完整的类型) -template -struct LazyType { - using type = T; -}; - -template -using LazyType_t = typename LazyType::type; - -// 使用场景:前向声明 -template -class Container { - using iterator = LazyType_t, - const T*, - T* - >::type>; -}; -``` - ------- - -## Embedded Best Practices - -### Practice 1: Hardware Register Type Aliases - -```cpp -// 寄存器宽度定义 -using Reg8 = volatile uint8_t; -using Reg16 = volatile uint16_t; -using Reg32 = volatile uint32_t; - -// 位域定义 -template -struct BitField { - Reg& reg; - static constexpr BitType mask = BitType::mask; - static constexpr uint8_t offset = BitType::offset; - - void set() { reg |= mask; } - void clear() { reg &= ~mask; } - bool is_set() const { return reg & mask; } - BitType get() const { return static_cast((reg >> offset) & mask); } -}; - -// 使用 -using GPIO_PIN5 = BitField; -``` - -### Practice 2: Compile-Time Buffer Configuration - -```cpp -// 缓冲区配置 -template -struct BufferConfig { - static constexpr std::size_t size = Size; - static constexpr std::size_t alignment = (Size >= 64) ? 8 : 4; - using storage_type = std::aligned_storage_t; -}; - -// 常用配置 -using RxBufferConfig = BufferConfig<256>; -using TxBufferConfig = BufferConfig<512>; -using LogBufferConfig = BufferConfig<1024>; - -// 缓冲区实现 -template -class Buffer { - alignas(Config::alignment) typename Config::storage_type storage_; -public: - // ... -}; -``` - -### Practice 3: DMA Descriptor Types - -```cpp -// DMA 描述符 -struct DMADescriptor { - std::uintptr_t src_addr; - std::uintptr_t dst_addr; - std::uint32_t control; - std::uint32_t reserved; -}; - -// DMA 通道类型别名 -using DMADescriptorArray = std::array; -using DMADescriptorPtr = std::uintptr_t; // 物理地址 -``` - -### Practice 4: Error Code Type Safety - -```cpp -// 错误码基类 -template -class ErrorCode { -public: - using value_type = Underlying; - - constexpr ErrorCode() : value_(0) {} - constexpr explicit ErrorCode(value_type v) : value_(v) {} - - constexpr value_type value() const { return value_; } - constexpr bool operator==(const ErrorCode& other) const { return value_ == other.value_; } - constexpr bool operator!=(const ErrorCode& other) const { return value_ != other.value_; } - -private: - value_type value_; -}; - -// 具体错误码类型 -using IOError = ErrorCode; -using SPIError = ErrorCode; -using I2CError = ErrorCode; - -// 使用 -I2CError i2c_write(uint8_t addr, const uint8_t* data, std::size_t len); -``` - ------- - -## Common Pitfalls - -### Pitfall 1: Alias Templates Are Not Types - -```cpp -template -using Ptr = T*; - -// ❌ 错误:不能前向声明别名模板 -// template -// using Ptr; - -// ✅ 正确:直接使用 -Ptr p; - -// ❌ 错误:不能特化别名模板 -// template<> -// using Ptr = void*; -``` - -### Pitfall 2: typename for Dependent Types - -```cpp -template -class Container { - // ❌ 错误:需要 typename - // using ValueType = T::value_type; - - // ✅ 正确 - using ValueType = typename T::value_type; - - // C++20 可以省略(如果确定是类型) - // using ValueType = T::value_type; -}; -``` - -### Pitfall 3: Visibility of Aliases vs typedef - -```cpp -class Base { -protected: - using value_type = int; -}; - -class Derived : public Base { - // typedef 的行为不同 - // typedef Base::value_type value_type; // 需要显式 - - // using 可以直接引入 - using Base::value_type; // OK -}; -``` - -### Pitfall 4: Function Type Aliases - -```cpp -// ❌ 混淆的写法 -// using Func = void(); // 这是函数类型,不是函数指针 - -// ✅ 正确的函数指针别名 -using FuncPtr = void(*)(); - -// 函数类型(用于模板参数) -template -void call(F f) { f(); } - -// 使用 -void my_func() {} -call(my_func); // OK,函数退化为指针 -call(my_func); // 也可以 -``` - -### Pitfall 5: Template Template Parameters - -```cpp -template -using MyVector = std::vector>; - -// ❌ 错误:MyVector 不是模板模板参数 -// template class Container> -// void process(Container c); - -// ✅ 正确:使用实际的模板 -template class Container> -void process(Container> c); -``` - ------- - -## New Features in C++14/17/20 - -### C++14: More _t Aliases - -```cpp -// C++11/14 标准 -using enable_if_t = typename enable_if<...>::type; -using conditional_t = typename conditional<...>::type; -using remove_reference_t = typename remove_reference<...>::type; - -// 可以自己定义 -template -using my_type_t = typename MyTrait::type; -``` - -### C++14: Variable Templates and Aliases - -```cpp -// 变量模板 -template -constexpr bool is_integral_v = std::is_integral::value; - -// 配合别名模板使用 -template -using enable_if_integral = std::enable_if_t>; - -// 使用 -template> -void process(T t); -``` - -### C++17: std::void_t - -```cpp -// C++17 标准 -template -using void_t = void; - -// SFINAE 检测 -template -struct has_member : std::false_type {}; - -template -struct has_member> : std::true_type {}; -``` - -### C++20: Concepts and Aliases - -```cpp -// Concept 定义 -template -concept Integral = std::is_integral_v; - -// 与别名模板配合 -template -using EnableIfIntegral = std::enable_if_t>; - -// 或者直接用 Concepts -template -void process(T t); - -// 缩写函数模板 -void process2(Integral auto t); -``` - -### C++20: requires Expressions - -```cpp -template -concept Iterable = requires(T t) { - { t.begin() } -> std::same_as; - { t.end() } -> std::same_as; -}; - -// 使用 -template -void process(T container); -``` - ------- - -## In Practice: Building a Complete Type System - -Let's use alias templates to build a type system commonly used in embedded projects. - -::: details Click to expand the full code - -```cpp - -#include - -#include - -#include - -#include - -// ==================== 基础类型别名 ==================== - -// 寄存器类型 -using Reg8 = std::uint8_t; -using Reg16 = std::uint16_t; -using Reg32 = std::uint32_t; - -// 大小类型 -using SizeType = std::size_t; - -// 时间戳类型 -using Timestamp = std::uint32_t; - -// ==================== 类型特征别名 ==================== - -// 检测是否为 POD 类型 -template -using IsPOD = std::is_pod; - -template -constexpr bool IsPOD_v = IsPOD::value; - -// 检测是否为标准布局 -template -using IsStandardLayout = std::is_standard_layout; - -template -constexpr bool IsStandardLayout_v = IsStandardLayout::value; - -// ==================== 条件类型选择 ==================== - -// DMA 传输类型 -template -using DMATransferType = std::conditional_t< - IsPOD_v && sizeof(T) <= 4, - T, - std::aligned_storage_t ->; - -// 中断处理函数类型 -template -using IRQHandler = void(*)(Args...); - -// ==================== 容器类型别名 ==================== - -// 固定大小数组 -template -using FixedArray = std::array; - -// 常用缓冲区大小 -template -using RxBuffer = FixedArray; - -template -using TxBuffer = FixedArray; - -template -using LogBuffer = FixedArray; - -// ==================== 智能指针别名 ==================== - -// 项目统一的智能指针配置 -template -using UniquePtr = std::unique_ptr; - -template -using SharedPtr = std::shared_ptr; - -// 单例指针 -template -using SingletonPtr = std::unique_ptr; - -// ==================== 错误处理类型 ==================== - -// 错误码基类 -template -class ErrorCode { -public: - using value_type = Underlying; - - constexpr ErrorCode() : value_(0) {} - constexpr explicit ErrorCode(value_type v) : value_(v) {} - - constexpr value_type value() const { return value_; } - -private: - value_type value_; -}; - -// 具体错误码类型 -using IOError = ErrorCode; -using SPIError = ErrorCode; -using I2CError = ErrorCode; -using ADCError = ErrorCode; - -// ==================== 传感器类型 ==================== - -// 传感器结果 -template -struct SensorResult { - T value; - bool valid; - Timestamp timestamp; -}; - -// 常用传感器结果类型 -using TemperatureResult = SensorResult; -using PressureResult = SensorResult; -using HumidityResult = SensorResult; -using AccelerationResult = SensorResult>; - -// 传感器接口 -template -class ISensor { -public: - using Result = ResultType; - - virtual ~ISensor() = default; - virtual Result read() = 0; - virtual bool calibrate() = 0; -}; - -// ==================== 回调类型 ==================== - -// 错误回调 -template -using ErrorCallback = void(*)(Args...); - -using SystemErrorCallback = ErrorCallback; - -// 数据回调 -template -using DataCallback = void(*)(const T&, Timestamp); - -// 完成回调 -using CompletionCallback = void(*)(bool success); - -// ==================== 配置类型 ==================== - -// UART 配置 -struct UARTConfig { - std::uint32_t baudrate; - std::uint8_t data_bits; - std::uint8_t stop_bits; - bool parity; -}; - -// SPI 配置 -struct SPIConfig { - std::uint32_t baudrate; - std::uint8_t mode; - bool lsb_first; -}; - -// I2C 配置 -struct I2CConfig { - std::uint32_t clock_speed; - std::uint16_t timeout_ms; -}; - -// ==================== 使用示例 ==================== - -class TemperatureSensor : public ISensor { -public: - Result read() override { - Result r; - r.value = 25.5f; - r.valid = true; - r.timestamp = 12345; - return r; - } - - bool calibrate() override { - return true; - } -}; - -void example_usage() { - // 使用智能指针 - auto sensor = UniquePtr>( - new TemperatureSensor() - ); - - // 读取传感器 - auto result = sensor->read(); - - // 使用缓冲区 - RxBuffer rx_data; - TxBuffer tx_data; - - // 使用错误码 - I2CError error = I2CError(1); - - // 使用回调 - SystemErrorCallback error_cb = [](int code, const char* msg) { - // 处理错误 - }; -} - -``` - -::: - ------- - -## Performance Analysis - -### Compile-Time vs Run-Time - -| Feature | Compile-Time Overhead | Run-Time Overhead | -|------|-----------|-----------| -| typedef | None | None | -| using alias | None | None | -| Alias template | Instantiation time | None | -| _t suffix types | Instantiation time | None | - -**Conclusion**: Alias templates **have no run-time overhead**; all computations are completed at compile time. - -### Code Size - -```cpp - -// 测试代码大小 -template -using Ptr = T*; - -Ptr p1; -Ptr p2; - -// 生成的代码与以下完全相同: -int* p1; -double* p2; - -// 别名模板只是语法糖,不产生额外代码 -``` - -### Compilation Time Impact - -```cpp -// 简单别名:编译时间可忽略 -using MyInt = int; - -// 别名模板:轻微增加编译时间 -template -using MyVector = std::vector; - -// 复杂的类型特征:可能显著增加编译时间 -template -using ComplexType = std::conditional_t< - std::conjunction_v, B, C>, - std::enable_if_t::value, E>, - F ->; -``` - -**Recommendation**: Keep alias templates simple in header files, and place complex type computations in source files. - ------- - -## Summary - -Alias templates are an important part of the modern C++ type system: - -| Feature | typedef | using | -|------|---------|-------| -| Basic alias | Supported | Supported (clearer) | -| Function pointer | Supported | Supported (clearer) | -| Template alias | **Not supported** | **Supported** | -| Template template parameter | Difficult | Intuitive | -| Readability | Poor | Better | - -**Key takeaways**: - -1. **Prefer `using`**: The syntax is clearer, and the functionality is more powerful. -2. **Understand the `_t` suffix convention**: `std::enable_if_t`, `std::conditional_t`, and others are all alias templates. -3. **Alias templates are syntactic sugar**: They do not incur run-time overhead and are only expanded at compile time. -4. **Use with type traits**: Combine them with `std::conditional_t` to achieve compile-time type selection. -5. **Simplify complex types**: Encapsulate complex template types into readable aliases. - -**Practical recommendations**: - -1. Uniformly use `using` instead of `typedef` in projects. -2. Define alias templates for commonly used template types. -3. Use the `_t` suffix convention to mark type aliases. -4. Keep alias templates simple in header files to avoid excessive nesting. -5. Leverage alias templates to implement type-safe interfaces. - -**In the next chapter**, we will explore **variadic templates**, learning how to handle an arbitrary number of template parameters to implement type-safe formatting functions and flexible delegate systems. diff --git a/documents/en/vol4-advanced/08-templates-and-inheritance-crtp.md b/documents/en/vol4-advanced/08-templates-and-inheritance-crtp.md deleted file mode 100644 index a8502347f..000000000 --- a/documents/en/vol4-advanced/08-templates-and-inheritance-crtp.md +++ /dev/null @@ -1,2131 +0,0 @@ ---- -chapter: 12 -cpp_standard: -- 11 -- 14 -- 17 -- 20 -description: In-depth understanding of the Curiously Recurring Template Pattern, static - polymorphism, and the Mixin pattern -difficulty: intermediate -order: 8 -platform: host -prerequisites: -- 'Chapter 12: 模板入门概述' -- 'Chapter 12: 函数模板详解' -- 'Chapter 2: CRTP vs 运行时多态' -reading_time_minutes: 49 -tags: -- cpp-modern -- host -- intermediate -title: 'Templates and Inheritance: CRTP and Static Polymorphism' ---- -# Modern C++ for Embedded Systems Tutorial—Templates and Inheritance: CRTP and Static Polymorphism - -## Introduction: When Templates Meet Inheritance - -Templates and inheritance are two core C++ features, but they are often treated as independent tools—templates for generic programming, and inheritance for object-oriented design. However, when combined, they produce powerful and elegant patterns. - -The most famous of these is the **CRTP (Curiously Recurring Template Pattern)**. The name sounds strange, but its applications are incredibly broad: from the Singleton pattern to object counting, from polymorphic cloning to interface injection. - -In this chapter, we will dive deep into the principles and applications of CRTP, compare the pros and cons of static and dynamic polymorphism, and learn how to use the Mixin pattern to add functionality to classes. - ------- - -## CRTP: Curiously Recurring Template Pattern - -### What is CRTP? - -CRTP is a C++ idiom whose core idea is: **a derived class passes itself as a template argument to its base class**. - -```cpp -template -class Base { -public: - void interface() { - // 基类通过转型调用派生类的实现 - static_cast(this)->implementation(); - } -}; - -class Derived : public Base { -public: - void implementation() { - // 派生类的具体实现 - } -}; -``` - -This pattern looks "curious" because: - -1. The derived class inherits from a base class that takes the derived class as a template argument -2. The base class accesses members of the derived class via `static_cast(this)` - -### Why do we need CRTP? - -CRTP solves three core problems: - -1. **Static polymorphism**: Achieving polymorphic behavior without using virtual functions -2. **Code reuse**: Implementing common logic in a base class while calling specific implementations in the derived class -3. **Compile-time type checking**: Ensuring that the derived class implements the required interface - -Let's understand this through an embedded scenario—a device driver framework: - -```cpp -// 设备基类(CRTP) -template -class DeviceBase { -public: - // 通用初始化流程 - void initialize() { - // 1. 硬件复位 - static_cast(this)->reset_hardware(); - - // 2. 配置寄存器 - static_cast(this)->configure_registers(); - - // 3. 校准 - static_cast(this)->calibrate(); - - // 4. 通用后处理 - static_cast(this)->set_initialized(); - } - - // 通用读取流程 - auto read() { - static_cast(this)->start_conversion(); - while (!static_cast(this)->is_ready()) { - // 等待转换完成 - } - return static_cast(this)->read_value(); - } -}; - -// ADC设备实现 -class ADCDevice : public DeviceBase { -public: - void reset_hardware() { - // ADC特定的复位逻辑 - } - - void configure_registers() { - // ADC特定的配置 - } - - void calibrate() { - // ADC特定的校准 - } - - void set_initialized() { - // 标记初始化完成 - } - - void start_conversion() { - // 启动ADC转换 - } - - bool is_ready() { - // 检查转换是否完成 - return true; - } - - uint16_t read_value() { - // 读取ADC值 - return 0; - } -}; - -// 使用 -ADCDevice adc; -adc.initialize(); -uint16_t value = adc.read(); -``` - -**Key points**: - -- All devices share the same initialization and read flow -- Each device provides its own specific implementation -- No virtual function calls; all calls can be inlined -- Compile-time type safety is guaranteed - -### The essence of CRTP: Static polymorphism - -CRTP implements **static polymorphism** (compile-time polymorphism), contrasting with the **dynamic polymorphism** (runtime polymorphism) implemented by virtual functions: - -| Feature | Dynamic Polymorphism (Virtual Functions) | Static Polymorphism (CRTP) | -|---------|------------------------------------------|----------------------------| -| Binding time | Runtime | Compile-time | -| Performance overhead | Vtable lookup + indirect call | Zero-overhead (can be inlined) | -| Memory overhead | One vptr per object | No extra memory | -| Type checking | Runtime (via vtable) | Compile-time | -| Code size | Smaller (one function implementation) | Larger (one per type) | -| Binary compatibility | Stable (ABI compatible) | Unstable (template instantiation) | -| Extensibility | New types can be added at runtime | Determined at compile-time | - ------- - -## How CRTP Works - -### Type casting explained - -The core of CRTP lies in `static_cast(this)`: - -```cpp -template -class Base { -public: - void method() { - Derived* d = static_cast(this); - d->impl(); // 调用派生类方法 - } -}; -``` - -**Why is this safe?** - -When `Derived` inherits from `Base`: - -1. The `this` pointer in `Base` actually points to a `Derived` object -2. `static_cast` does not change the pointer value; it only changes the compiler's understanding of the type -3. This is similar to a `void*` to concrete type conversion, but safer - -**Layout guarantees**: - -```cpp -class Derived : public Base { - int data; -}; - -// 内存布局: -// [Base部分] [Derived部分] -// ↑ ↑ -// this 派生类数据 -``` - -### Compile-time type checking - -CRTP checks at compile-time whether the derived class implements the required interface: - -```cpp -template -class Base { -public: - void interface() { - // 如果Derived没有实现implementation(),编译失败 - static_cast(this)->implementation(); - } -}; - -class Derived : public Base { - // 未实现implementation() -}; - -// 编译错误:'class Derived' has no member named 'implementation' -``` - -This check occurs at instantiation time, not at template definition time. - -### Complete example: Polymorphic cloning - -A classic application of CRTP is implementing a polymorphic `clone()` method: - -```cpp -template -class Cloneable { -public: - // 克隆接口,返回正确的派生类类型 - [[nodiscard]] Derived* clone() const { - return new Derived(static_cast(*this)); - } - - [[nodiscard]] std::unique_ptr unique_clone() const { - return std::make_unique(static_cast(*this)); - } -}; - -class Sensor : public Cloneable { -public: - Sensor(const Sensor& other) = default; - // ... -}; - -class TemperatureSensor : public Cloneable { -public: - TemperatureSensor(const TemperatureSensor& other) = default; - // ... -}; - -// 使用 -TemperatureSensor ts1; -auto ts2 = ts1.unique_clone(); // 返回unique_ptr -``` - -Compared to the virtual function version: - -```cpp -// 虚函数版本 -class Sensor { -public: - virtual Sensor* clone() const = 0; - virtual ~Sensor() = default; -}; - -class TemperatureSensor : public Sensor { -public: - TemperatureSensor* clone() const override { - return new TemperatureSensor(*this); - } -}; - -// 使用 -TemperatureSensor ts1; -auto ts2 = std::unique_ptr(ts1.clone()); // 返回unique_ptr,丢失了具体类型 -``` - -The advantage of the CRTP version: **the return type is the concrete derived class type, requiring no additional type casting**. - ------- - -## CRTP in Practice: Singleton Base Class - -### Problem analysis - -The Singleton pattern is one of the most commonly used design patterns, but every singleton class requires writing the same boilerplate code: - -```cpp -class MySingleton { -public: - MySingleton(const MySingleton&) = delete; - MySingleton& operator=(const MySingleton&) = delete; - - static MySingleton& instance() { - static MySingleton inst; - return inst; - } - -private: - MySingleton() = default; -}; -``` - -### CRTP solution - -Using CRTP, we can implement a generic singleton base class: - -```cpp -template -class Singleton { -public: - Singleton(const Singleton&) = delete; - Singleton& operator=(const Singleton&) = delete; - - static Derived& instance() { - // C++11保证局部静态变量的线程安全初始化 - static Derived inst; - return inst; - } - -protected: - Singleton() = default; - ~Singleton() = default; -}; -``` - -### Complete implementation - -```cpp -#include - -template -class Singleton { -public: - // 禁止拷贝和移动 - Singleton(const Singleton&) = delete; - Singleton& operator=(const Singleton&) = delete; - Singleton(Singleton&&) = delete; - Singleton& operator=(Singleton&&) = delete; - - // 获取单例引用 - [[nodiscard]] static Derived& instance() { - static Derived inst; - return inst; - } - - // 获取单例指针(可选,用于更简洁的访问) - [[nodiscard]] static Derived* ptr() { - return &instance(); - } - -protected: - Singleton() = default; - virtual ~Singleton() = default; -}; - -// 使用示例 -class Logger : public Singleton { - // 让基类可以访问构造函数 - friend class Singleton; - -public: - void log(const char* msg) { - // 日志实现 - } - -private: - Logger() { - // 初始化日志系统 - } - - ~Logger() override { - // 清理资源 - } -}; - -// 使用 -int main() { - Logger::instance().log("System starting"); - Logger::ptr()->log("Another message"); - return 0; -} -``` - -### Embedded version (Meyer's Singleton) - -In embedded systems, we might need more fine-grained control: - -```cpp -template -class EmbeddedSingleton { -public: - EmbeddedSingleton(const EmbeddedSingleton&) = delete; - EmbeddedSingleton& operator=(const EmbeddedSingleton&) = delete; - - static Derived& instance() { - std::call_once(init_flag_, &EmbeddedSingleton::init); - return *instance_; - } - - // 手动初始化(用于控制初始化时机) - static void init() { - if (!instance_) { - instance_ = new Derived(); - } - } - - // 手动销毁(用于控制销毁时机) - static void destroy() { - delete instance_; - instance_ = nullptr; - } - -protected: - EmbeddedSingleton() = default; - virtual ~EmbeddedSingleton() { - instance_ = nullptr; - } - -private: - static Derived* instance_; - static std::once_flag init_flag_; - static Mutex mutex_; -}; - -template -Derived* EmbeddedSingleton::instance_ = nullptr; - -template -std::once_flag EmbeddedSingleton::init_flag_; - -template -Mutex EmbeddedSingleton::mutex_; -``` - -### Thread safety guarantees - -Since C++11, the initialization of local static variables is thread-safe: - -```cpp -static Derived inst; // 编译器保证线程安全的初始化 -``` - -But if you need to support older C++ standards or require more fine-grained control, you can use `std::call_once`: - -```cpp -template -class ThreadSafeSingleton { -public: - static Derived& instance() { - std::call_once(init_flag_, []() { - instance_ = new Derived(); - }); - return *instance_; - } - -private: - static Derived* instance_; - static std::once_flag init_flag_; -}; - -template -Derived* ThreadSafeSingleton::instance_ = nullptr; - -template -std::once_flag ThreadSafeSingleton::init_flag_; -``` - -### Practical application: Device manager - -```cpp -class DeviceManager : public Singleton { - friend class Singleton; - -public: - void register_device(const char* name, void* device) { - devices_[device_count_] = {name, device}; - device_count_++; - } - - void* get_device(const char* name) { - for (size_t i = 0; i < device_count_; ++i) { - if (std::strcmp(devices_[i].name, name) == 0) { - return devices_[i].device; - } - } - return nullptr; - } - -private: - struct DeviceEntry { - const char* name; - void* device; - }; - - DeviceEntry devices_[16]; - size_t device_count_ = 0; - - DeviceManager() = default; -}; -``` - -::: details View the complete singleton implementation example - -```cpp -#include -#include - -template -class Singleton { -public: - Singleton(const Singleton&) = delete; - Singleton& operator=(const Singleton&) = delete; - Singleton(Singleton&&) = delete; - Singleton& operator=(Singleton&&) = delete; - - [[nodiscard]] static Derived& instance() { - static Derived inst; - return inst; - } - - [[nodiscard]] static Derived* ptr() { - return &instance(); - } - -protected: - Singleton() = default; - virtual ~Singleton() = default; -}; - -class DeviceManager : public Singleton { - friend class Singleton; - -public: - bool register_device(const char* name, void* device) { - if (device_count_ >= max_devices_) { - return false; - } - devices_[device_count_] = {name, device}; - device_count_++; - return true; - } - - void* get_device(const char* name) const { - for (size_t i = 0; i < device_count_; ++i) { - if (std::strcmp(devices_[i].name, name) == 0) { - return devices_[i].device; - } - } - return nullptr; - } - - size_t device_count() const { - return device_count_; - } - -private: - struct DeviceEntry { - const char* name; - void* device; - }; - - static constexpr size_t max_devices_ = 32; - DeviceEntry devices_[max_devices_]; - size_t device_count_ = 0; - - DeviceManager() = default; - ~DeviceManager() override = default; -}; - -// 使用示例 -int main() { - auto& dm = DeviceManager::instance(); - - int uart1 = 1; - int spi1 = 2; - int i2c1 = 3; - - dm.register_device("UART1", &uart1); - dm.register_device("SPI1", &spi1); - dm.register_device("I2C1", &i2c1); - - void* dev = dm.get_device("UART1"); - return 0; -} - -``` - -::: - ------- - -## CRTP in Practice: Object Counter - -### Application scenarios - -In embedded systems, we often need to: - -- Track how many objects of a certain class have been created -- Detect memory leaks -- Monitor resource usage -- Implement object pools - -### Basic implementation - -```cpp - -template -class ObjectCounter { -public: - static size_t get_count() { - return count_; - } - -protected: - ObjectCounter() { - ++count_; - } - - ObjectCounter(const ObjectCounter&) { - ++count_; - } - - ObjectCounter(ObjectCounter&&) { - ++count_; - } - - ~ObjectCounter() { - --count_; - } - -private: - static size_t count_; -}; - -template -size_t ObjectCounter::count_ = 0; -``` - -### Usage example - -```cpp -class Sensor : public ObjectCounter { -public: - Sensor() = default; - // ... -}; - -void test_sensor_counting() { - printf("Initial: %zu sensors\n", Sensor::get_count()); // 0 - - { - Sensor s1; - printf("After s1: %zu sensors\n", Sensor::get_count()); // 1 - - Sensor s2; - printf("After s2: %zu sensors\n", Sensor::get_count()); // 2 - - { - Sensor s3; - printf("After s3: %zu sensors\n", Sensor::get_count()); // 3 - } - printf("After s3 destroyed: %zu sensors\n", Sensor::get_count()); // 2 - } - printf("After all destroyed: %zu sensors\n", Sensor::get_count()); // 0 -} -``` - -### Advanced: Move and copy counting - -```cpp -template -class DetailedObjectCounter { -public: - static size_t get_alive_count() { - return alive_count_; - } - - static size_t get_total_created() { - return total_created_; - } - - static size_t get_total_copied() { - return copy_count_; - } - - static size_t get_total_moved() { - return move_count_; - } - - static void reset_stats() { - alive_count_ = 0; - total_created_ = 0; - copy_count_ = 0; - move_count_ = 0; - } - -protected: - DetailedObjectCounter() noexcept { - ++alive_count_; - ++total_created_; - } - - ~DetailedObjectCounter() { - --alive_count_; - } - - DetailedObjectCounter(const DetailedObjectCounter&) noexcept { - ++alive_count_; - ++total_created_; - ++copy_count_; - } - - DetailedObjectCounter(DetailedObjectCounter&&) noexcept { - ++alive_count_; - ++total_created_; - ++move_count_; - } - - DetailedObjectCounter& operator=(const DetailedObjectCounter&) = default; - DetailedObjectCounter& operator=(DetailedObjectCounter&&) = default; - -private: - static size_t alive_count_; - static size_t total_created_; - static size_t copy_count_; - static size_t move_count_; -}; - -template -size_t DetailedObjectCounter::alive_count_ = 0; - -template -size_t DetailedObjectCounter::total_created_ = 0; - -template -size_t DetailedObjectCounter::copy_count_ = 0; - -template -size_t DetailedObjectCounter::move_count_ = 0; -``` - -### Memory leak detection - -```cpp -template -class LeakDetector : public ObjectCounter { -public: - ~LeakDetector() { - if (ObjectCounter::get_count() > 0) { - // 在实际系统中,这里可能记录日志或触发警告 - printf("Warning: %zu instances of %s still alive!\n", - ObjectCounter::get_count(), - Derived::class_name()); - } - } -}; - -class Buffer : public LeakDetector { -public: - static const char* class_name() { - return "Buffer"; - } - - Buffer(size_t size) : data_(new uint8_t[size]), size_(size) {} - - ~Buffer() { - delete[] data_; - } - -private: - uint8_t* data_; - size_t size_; -}; -``` - -### Resource monitoring - -```cpp -template -class BoundedCounter : public ObjectCounter { -public: - static bool can_create() { - return ObjectCounter::get_count() < MaxInstances; - } - - static size_t remaining_capacity() { - return MaxInstances - ObjectCounter::get_count(); - } - -protected: - BoundedCounter() { - if (!can_create()) { - throw std::runtime_error("Maximum instances reached"); - } - } -}; - -// 使用:限制最多创建8个传感器 -class LimitedSensor : public BoundedCounter { -public: - LimitedSensor() = default; -}; -``` - -::: details View the complete object counter implementation - -```cpp -#include -#include -#include - -template -class ObjectCounter { -public: - static size_t get_count() { - return count_; - } - -protected: - ObjectCounter() { - ++count_; - } - - ObjectCounter(const ObjectCounter&) { - ++count_; - } - - ObjectCounter(ObjectCounter&&) { - ++count_; - } - - ~ObjectCounter() { - --count_; - } - -private: - static size_t count_; -}; - -template -size_t ObjectCounter::count_ = 0; - -// 检测泄漏的版本 -template -class LeakDetector : public ObjectCounter { -public: - ~LeakDetector() { - if (ObjectCounter::get_count() > 0) { - printf("[LeakDetector] Warning: %zu instances of %s leaked!\n", - ObjectCounter::get_count(), - Derived::static_class_name()); - } - } -}; - -// 示例类 -class Sensor : public LeakDetector { -public: - static const char* static_class_name() { - return "Sensor"; - } - - Sensor(int id) : id_(id) { - printf("[Sensor] Sensor %d created. Total: %zu\n", - id_, get_count()); - } - - ~Sensor() { - printf("[Sensor] Sensor %d destroyed. Remaining: %zu\n", - id_, get_count()); - } - -private: - int id_; -}; - -// 限制数量的版本 -template -class BoundedCounter : public ObjectCounter { -public: - static constexpr size_t max_instances = MaxInstances; - static size_t remaining() { - return MaxInstances - ObjectCounter::get_count(); - } - -protected: - BoundedCounter() { - if (!can_create()) { - throw std::runtime_error("Maximum instances exceeded"); - } - } - -private: - static bool can_create() { - return ObjectCounter::get_count() < MaxInstances; - } -}; - -class LimitedBuffer : public BoundedCounter { -public: - LimitedBuffer(size_t size) : size_(size) { - printf("[LimitedBuffer] Created %zu-byte buffer. Remaining capacity: %zu\n", - size_, remaining()); - } - - ~LimitedBuffer() { - printf("[LimitedBuffer] Destroyed buffer. Remaining: %zu\n", remaining()); - } - -private: - size_t size_; -}; - -int main() { - printf("=== Object Counter Demo ===\n"); - - { - Sensor s1(1); - Sensor s2(2); - { - Sensor s3(3); - } - Sensor s4(4); - } - - printf("\n=== Bounded Buffer Demo ===\n"); - - try { - LimitedBuffer b1(1024); - LimitedBuffer b2(2048); - LimitedBuffer b3(4096); - LimitedBuffer b4(8192); - printf("Created 4 buffers successfully\n"); - - LimitedBuffer b5(16384); // 应该抛出异常 - } catch (const std::exception& e) { - printf("Exception: %s\n", e.what()); - } - - return 0; -} - -``` - -::: - ------- - -## Mixin Pattern - -### What is a Mixin? - -A Mixin is a pattern that composes functionality through inheritance, allowing you to "mix in" reusable features into a class. CRTP is the perfect tool for implementing Mixins. - -### Basic Mixin - -```cpp - -// Printable Mixin:为类添加打印功能 -template -class Printable { -public: - void print() const { - const Derived* d = static_cast(this); - d->print_to(std::cout); - } - - void print_to(std::ostream& os) const { - const Derived* d = static_cast(this); - d->print_to(os); - } -}; - -// Comparable Mixin:为类添加比较功能 -template -class Comparable { -public: - bool operator<(const Comparable& other) const { - const Derived* d = static_cast(this); - const Derived* o = static_cast(&other); - return d->compare(*o) < 0; - } - - bool operator==(const Comparable& other) const { - const Derived* d = static_cast(this); - const Derived* o = static_cast(&other); - return d->compare(*o) == 0; - } - - bool operator!=(const Comparable& other) const { - return !(*this == other); - } - - bool operator<=(const Comparable& other) const { - return !(other < *this); - } - - bool operator>(const Comparable& other) const { - return other < *this; - } - - bool operator>=(const Comparable& other) const { - return !(*this < other); - } -}; -``` - -### Multiple Mixin composition - -A class can inherit from multiple Mixins: - -```cpp -class Sensor : public Printable, - public Comparable, - public ObjectCounter { -public: - Sensor(int id, int value) : id_(id), value_(value) {} - - void print_to(std::ostream& os) const { - os << "Sensor{id=" << id_ << ", value=" << value_ << "}"; - } - - int compare(const Sensor& other) const { - if (id_ != other.id_) { - return id_ - other.id_; - } - return value_ - other.value_; - } - -private: - int id_; - int value_; -}; - -// 使用 -void demo_mixins() { - Sensor s1(1, 100); - Sensor s2(2, 200); - - s1.print(); // 来自Printable - if (s1 < s2) { // 来自Comparable - printf("s1 < s2\n"); - } - - printf("Total sensors: %zu\n", Sensor::get_count()); // 来自ObjectCounter -} -``` - -### Embedded application: State tracking Mixin - -```cpp -template -class StateTracking { -public: - enum class State { - Uninitialized, - Initializing, - Ready, - Running, - Error, - Suspended - }; - - State get_state() const { - return state_; - } - - const char* get_state_name() const { - switch (state_) { - case State::Uninitialized: return "Uninitialized"; - case State::Initializing: return "Initializing"; - case State::Ready: return "Ready"; - case State::Running: return "Running"; - case State::Error: return "Error"; - case State::Suspended: return "Suspended"; - default: return "Unknown"; - } - } - - bool is_ready() const { - return state_ == State::Ready; - } - - bool is_running() const { - return state_ == State::Running; - } - - bool has_error() const { - return state_ == State::Error; - } - -protected: - void set_state(State new_state) { - if (state_ != new_state) { - State old_state = state_; - state_ = new_state; - on_state_changed(old_state, new_state); - } - } - - virtual void on_state_changed(State old_state, State new_state) { - // 默认空实现,派生类可以重写 - (void)old_state; - (void)new_state; - } - -private: - State state_ = State::Uninitialized; -}; - -// 使用 -class Motor : public StateTracking { -public: - void initialize() { - set_state(State::Initializing); - // 初始化逻辑 - set_state(State::Ready); - } - - void start() { - if (is_ready()) { - set_state(State::Running); - } - } - - void stop() { - set_state(State::Ready); - } - - void on_state_changed(State old_state, State new_state) override { - printf("Motor state: %s -> %s\n", - state_to_string(old_state), - state_to_string(new_state)); - } - -private: - const char* state_to_string(State s) { - switch (s) { - case State::Initializing: return "Initializing"; - case State::Ready: return "Ready"; - case State::Running: return "Running"; - default: return "Other"; - } - } -}; -``` - -### Thread safety Mixin - -```cpp -template -class ThreadSafe { -protected: - using Lock = std::lock_guard; - - Mutex& mutex() { - return mutex_; - } - - const Mutex& mutex() const { - return mutex_; - } - -private: - mutable Mutex mutex_; -}; - -// 使用 -class ThreadSafeCounter : public ThreadSafe { -public: - void increment() { - Lock lock(mutex()); - ++value_; - } - - int get() const { - Lock lock(mutex()); - return value_; - } - -private: - int value_ = 0; -}; -``` - -### Configuration management Mixin - -```cpp -template -class Configurable { -public: - template - void set(const char* key, const T& value) { - config_[key] = ConfigValue(value); - } - - template - T get(const char* key, const T& default_value) const { - auto it = config_.find(key); - if (it != config_.end()) { - return std::any_cast(it->second); - } - return default_value; - } - - bool has(const char* key) const { - return config_.find(key) != config_.end(); - } - -protected: - using ConfigValue = std::any; - using ConfigMap = std::unordered_map; - - ConfigMap config_; -}; - -// 使用 -class ConfigurableSensor : public Configurable { -public: - void apply_config() { - int sample_rate = get("sample_rate", 1000); - bool enabled = get("enabled", true); - // 应用配置 - } -}; -``` - -::: details View the complete Mixin example - -```cpp -#include -#include -#include -#include -#include - -// Printable Mixin -template -class Printable { -public: - void print() const { - static_cast(this)->print_to(std::cout); - } - - void print_to(std::ostream& os) const { - static_cast(this)->print_to(os); - } -}; - -// Observable Mixin:观察者模式 -template -class Observable { -public: - using Callback = std::function; - - void subscribe(Callback callback) { - callbacks_.push_back(std::move(callback)); - } - - void notify() const { - const Derived& d = *static_cast(this); - for (const auto& cb : callbacks_) { - cb(d); - } - } - -protected: - ~Observable() = default; - -private: - std::vector callbacks_; -}; - -// 验证Mixin -template -class Validatable { -public: - bool is_valid() const { - return static_cast(this)->validate(); - } - - explicit operator bool() const { - return is_valid(); - } -}; - -// 组合多个Mixin的示例类 -class TemperatureSensor : public Printable, - public Observable, - public Validatable { -public: - TemperatureSensor(float min, float max) - : min_temp_(min), max_temp_(max), current_temp_(0.0f) {} - - void set_temperature(float temp) { - current_temp_ = temp; - if (is_valid()) { - notify(); - } - } - - float get_temperature() const { - return current_temp_; - } - - // Printable实现 - void print_to(std::ostream& os) const { - os << "TemperatureSensor{temp=" << current_temp_ - << ", range=[" << min_temp_ << "," << max_temp_ << "]}"; - } - - // Validatable实现 - bool validate() const { - return current_temp_ >= min_temp_ && current_temp_ <= max_temp_; - } - -private: - float min_temp_; - float max_temp_; - float current_temp_; -}; - -int main() { - TemperatureSensor sensor(-40.0f, 125.0f); - - // 订阅温度变化 - sensor.subscribe([](const TemperatureSensor& s) { - std::cout << "Temperature changed: "; - s.print(); - std::cout << std::endl; - }); - - // 设置有效温度 - sensor.set_temperature(25.0f); - sensor.print(); // TemperatureSensor{temp=25, range=[-40,125]} - - std::cout << "Valid: " << (sensor ? "yes" : "no") << std::endl; - - // 设置无效温度(超出范围) - sensor.set_temperature(150.0f); - std::cout << "Valid: " << (sensor ? "yes" : "no") << std::endl; - - return 0; -} - -``` - -::: - ------- - -## Performance Analysis: CRTP vs. Virtual Functions - -### Test scenario - -Let's compare the performance differences between CRTP and virtual functions through a practical test: - -```cpp - -// 虚函数版本 -class ShapeVirtual { -public: - virtual ~ShapeVirtual() = default; - virtual double area() const = 0; -}; - -class CircleVirtual : public ShapeVirtual { -public: - CircleVirtual(double r) : radius_(r) {} - double area() const override { - return 3.14159 * radius_ * radius_; - } -private: - double radius_; -}; - -// CRTP版本 -template -class ShapeCRTP { -public: - double area() const { - return static_cast(this)->area_impl(); - } -}; - -class CircleCRTP : public ShapeCRTP { -public: - CircleCRTP(double r) : radius_(r) {} - double area_impl() const { - return 3.14159 * radius_ * radius_; - } -private: - double radius_; -}; -``` - -### Assembly code comparison - -Assembly output using the `-O2` optimization level (ARM GCC): - -**Virtual function version call**: - -```asm -; vtable查找 + 间接调用 -ldr r0, [r0] ; 加载对象指针 -ldr r0, [r0, #4] ; 加载vtable指针 -ldr r0, [r0, #8] ; 从vtable加载area()函数指针 -bx r0 ; 间接跳转 -``` - -**CRTP version call**: - -```asm -; 直接调用(可能内联) -; 当类型已知时,编译器直接内联计算 -vmul.f64 d0, d0, d0 ; r * r -vmul.f64 d0, d0, d1 ; * pi -``` - -### Performance test results - -Test results on an STM32F4 (180MHz ARM Cortex-M4): - -| Test Scenario | Virtual Functions (ns) | CRTP (ns) | Improvement | -|---------------|------------------------|-----------|-------------| -| Single call | 45 | 12 | 3.75x | -| Loop 1000 times | 42000 | 11000 | 3.82x | -| After loop inlining | 42000 | 3000 | 14x | - -**Key findings**: - -1. CRTP has a three to four times performance advantage on simple calls -2. When the compiler can fully inline, the advantage expands to 14 times -3. The indirect calls of virtual functions hinder inlining optimizations - -### Complete benchmark code - -```cpp -#include -#include -#include -#include - -// ... 上面的类定义 ... - -constexpr size_t iterations = 1000000; - -double benchmark_virtual(const std::vector& shapes) { - auto start = std::chrono::high_resolution_clock::now(); - - double sum = 0; - for (size_t i = 0; i < iterations; ++i) { - for (auto* shape : shapes) { - sum += shape->area(); - } - } - - auto end = std::chrono::high_resolution_clock::now(); - auto duration = std::chrono::duration_cast(end - start); - - std::cout << "Virtual sum: " << sum << ", time: " << duration.count() << " us\n"; - return duration.count(); -} - -template -double benchmark_crtp(CircleCRTP (&circles)[N]) { - auto start = std::chrono::high_resolution_clock::now(); - - double sum = 0; - for (size_t i = 0; i < iterations; ++i) { - for (auto& circle : circles) { - sum += circle.area(); - } - } - - auto end = std::chrono::high_resolution_clock::now(); - auto duration = std::chrono::duration_cast(end - start); - - std::cout << "CRTP sum: " << sum << ", time: " << duration.count() << " us\n"; - return duration.count(); -} - -int main() { - constexpr size_t num_shapes = 10; - - // 虚函数版本 - std::vector vshapes; - std::vector vcircles; - vcircles.reserve(num_shapes); - for (size_t i = 0; i < num_shapes; ++i) { - vcircles.emplace_back(1.0 + i * 0.1); - vshapes.push_back(&vcircles.back()); - } - - // CRTP版本 - CircleCRTP ccircles[num_shapes]; - for (size_t i = 0; i < num_shapes; ++i) { - ccircles[i] = CircleCRTP(1.0 + i * 0.1); - } - - // 运行基准测试 - double vtime = benchmark_virtual(vshapes); - double ctime = benchmark_crtp(ccircles); - - std::cout << "Speedup: " << (vtime / ctime) << "x\n"; - - return 0; -} -``` - -### Memory footprint comparison - -| Aspect | Virtual Functions | CRTP | -|--------|-------------------|------| -| Extra overhead per object | One pointer (vptr, 4-8 bytes) | 0 bytes | -| Vtable storage | One vtable per class (Flash) | None | -| Code size | Smaller (functions are shared) | Larger (one copy per type) | -| Inlining possibility | Low (indirect call) | High (direct call) | - -### When to choose CRTP vs. virtual functions - -**Scenarios for choosing CRTP**: - -- Performance-critical code (ISRs, real-time loops) -- Types are determined at compile-time -- Inlining optimization is needed -- RAM is constrained (avoid vptr overhead) -- No need to dynamically replace implementations at runtime - -**Scenarios for choosing virtual functions**: - -- Runtime polymorphism is needed (plugin systems) -- Types are not determined at compile-time -- Accessing objects through interfaces -- ABI stability is required -- Code size is more important than performance - -::: details View the complete performance test code - -```cpp -#include -#include -#include -#include -#include - -// 虚函数版本 -class ShapeVirtual { -public: - virtual ~ShapeVirtual() = default; - virtual double area() const = 0; - virtual double perimeter() const = 0; -}; - -class RectangleVirtual : public ShapeVirtual { -public: - RectangleVirtual(double w, double h) : width_(w), height_(h) {} - - double area() const override { - return width_ * height_; - } - - double perimeter() const override { - return 2 * (width_ + height_); - } - -private: - double width_; - double height_; -}; - -class CircleVirtual : public ShapeVirtual { -public: - explicit CircleVirtual(double r) : radius_(r) {} - - double area() const override { - return 3.14159265359 * radius_ * radius_; - } - - double perimeter() const override { - return 2 * 3.14159265359 * radius_; - } - -private: - double radius_; -}; - -// CRTP版本 -template -class ShapeCRTP { -public: - double area() const { - return static_cast(this)->area_impl(); - } - - double perimeter() const { - return static_cast(this)->perimeter_impl(); - } -}; - -class RectangleCRTP : public ShapeCRTP { -public: - RectangleCRTP(double w, double h) : width_(w), height_(h) {} - - double area_impl() const { - return width_ * height_; - } - - double perimeter_impl() const { - return 2 * (width_ + height_); - } - -private: - double width_; - double height_; -}; - -class CircleCRTP : public ShapeCRTP { -public: - explicit CircleCRTP(double r) : radius_(r) {} - - double area_impl() const { - return 3.14159265359 * radius_ * radius_; - } - - double perimeter_impl() const { - return 2 * 3.14159265359 * radius_; - } - -private: - double radius_; -}; - -// 基准测试工具 -class Benchmark { -public: - static constexpr size_t iterations = 10000000; - - template - static double run(const char* name, Func&& func) { - // 预热 - for (int i = 0; i < 1000; ++i) { - func(); - } - - auto start = std::chrono::high_resolution_clock::now(); - - volatile double result = 0; // volatile防止优化 - for (size_t i = 0; i < iterations; ++i) { - result += func(); - } - - auto end = std::chrono::high_resolution_clock::now(); - auto duration = std::chrono::duration_cast(end - start); - - double avg_ns = static_cast(duration.count()) / iterations; - - std::cout << std::left << std::setw(30) << name - << " Avg: " << std::setw(8) << std::fixed << std::setprecision(2) << avg_ns << " ns" - << " Total: " << std::setw(8) << duration.count() / 1000000 << " ms" - << " Result: " << result << "\n"; - - return avg_ns; - } -}; - -// 测试多态容器 -void test_polymorphic_container() { - std::cout << "\n=== Polymorphic Container Test ===\n"; - - std::vector> vshapes; - vshapes.push_back(std::make_unique(1.0)); - vshapes.push_back(std::make_unique(2.0, 3.0)); - vshapes.push_back(std::make_unique(2.5)); - - double sum = 0; - auto start = std::chrono::high_resolution_clock::now(); - - for (size_t i = 0; i < Benchmark::iterations; ++i) { - for (const auto& shape : vshapes) { - sum += shape->area(); - } - } - - auto end = std::chrono::high_resolution_clock::now(); - auto duration = std::chrono::duration_cast(end - start); - - std::cout << "Virtual polymorphic: " << duration.count() << " ms\n"; - - // CRTP无法在运行时多态容器中使用 - // 这是虚函数的一个优势 -} - -// 单类型性能测试 -void test_single_type_performance() { - std::cout << "\n=== Single Type Performance Test ===\n"; - - constexpr double radius = 2.5; - - // 虚函数版本 - CircleVirtual vcircle(radius); - - double vtime = Benchmark::run("Virtual (single type)", [&]() { - return vcircle.area(); - }); - - // CRTP版本 - CircleCRTP ccircle(radius); - - double ctime = Benchmark::run("CRTP (single type)", [&]() { - return ccircle.area(); - }); - - std::cout << "\nSpeedup: " << (vtime / ctime) << "x\n"; -} - -// 内联测试 -void test_inlining() { - std::cout << "\n=== Inlining Test ===\n"; - - constexpr double radius = 1.5; - CircleCRTP circle(radius); - - // 直接计算(基准) - double baseline = Benchmark::run("Direct calculation", [&]() { - return 3.14159265359 * radius * radius; - }); - - // CRTP(应该内联到与直接计算相同) - double crtp = Benchmark::run("CRTP (should inline)", [&]() { - return circle.area(); - }); - - std::cout << "\nCRTP overhead vs direct: " << ((crtp / baseline) - 1.0) * 100 << "%\n"; -} - -int main() { - std::cout << "CRTP vs Virtual Performance Benchmark\n"; - std::cout << "Iterations: " << Benchmark::iterations << "\n\n"; - - test_single_type_performance(); - test_inlining(); - test_polymorphic_container(); - - std::cout << "\n=== Memory Usage ===\n"; - std::cout << "sizeof(CircleVirtual): " << sizeof(CircleVirtual) << " bytes (includes vptr)\n"; - std::cout << "sizeof(CircleCRTP): " << sizeof(CircleCRTP) << " bytes (no vptr)\n"; - std::cout << "sizeof(RectangleVirtual): " << sizeof(RectangleVirtual) << " bytes\n"; - std::cout << "sizeof(RectangleCRTP): " << sizeof(RectangleCRTP) << " bytes\n"; - - return 0; -} - -``` - -::: - ------- - -## Advanced CRTP Techniques - -### 1. Perfect forwarding to the derived class - -```cpp - -template -class Base { -public: - template - auto construct(Args&&... args) { - return static_cast(this)->construct_impl( - std::forward(args)...); - } -}; - -class Derived : public Base { -public: - template - T construct_impl(T&& arg) { - return T{std::forward(arg)}; - } -}; -``` - -### 2. CRTP and decltype(auto) - -```cpp -template -class Base { -public: - decltype(auto) get_value() { - return static_cast(this)->get_value_impl(); - } -}; - -class Derived : public Base { -public: - int& get_value_impl() { - return value_; - } - -private: - int value_; -}; - -// get_value()返回int&,保留引用语义 -``` - -### 3. Constraining the derived class interface - -Using C++20 Concepts, we can constrain the CRTP interface: - -```cpp -template -concept CRTPDerived = requires(Derived d) { - { d.interface() } -> std::same_as; -}; - -template -class Base { -public: - void wrapper() { - static_cast(this)->interface(); - } -}; -``` - -### 4. CRTP and type traits - -```cpp -template -class Base { -public: - using derived_type = Derived; - - Derived& derived() { - return static_cast(*this); - } - - const Derived& derived() const { - return static_cast(*this); - } -}; - -// 使用 -class Derived : public Base { -public: - void method() { - // 可以使用derived()代替static_cast - derived().implementation(); - } -}; -``` - -### 5. Multi-parameter CRTP - -```cpp -template -class MultiMixinBase : public Mixins... { -public: - void dispatch() { - // 调用所有Mixin的方法 - (static_cast(this)->handle(), ...); - } -}; - -template -class Logger { -public: - void handle() { - std::cout << "Logging\n"; - } -}; - -template -class Validator { -public: - void handle() { - std::cout << "Validating\n"; - } -}; - -class MyClass : public MultiMixinBase, Validator> { -}; -``` - ------- - -## Common Pitfalls and Solutions - -### Pitfall 1: Forgetting to provide a derived class implementation - -```cpp -template -class Base { -public: - void interface() { - static_cast(this)->implementation(); // 编译期检查 - } -}; - -class Derived : public Base { - // 未实现implementation() -}; - -// 编译错误:没有成员'implementation' -``` - -**Solution**: Use static_assert to provide better error messages: - -```cpp -template -class Base { -public: - void interface() { - static_assert(requires { static_cast(this)->implementation(); }, - "Derived must implement implementation()"); - static_cast(this)->implementation(); - } -}; -``` - -### Pitfall 2: Private inheritance access issues - -```cpp -template -class Base { -public: - void method() { - // 如果Derived私有继承,可能无法访问 - static_cast(this)->impl(); - } -}; - -class Derived : private Base { // 私有继承 -public: - void impl() {} -}; -``` - -**Solution**: Use a using declaration or friend: - -```cpp -template -class Base { -public: - void method() { - static_cast(this)->impl(); - } -}; - -class Derived : private Base { - friend class Base; // 声明友元 -public: - void impl() {} -}; -``` - -### Pitfall 3: Diamond inheritance - -```cpp -template -class A {}; - -template -class B : public A {}; - -template -class C : public A {}; - -class D : public B, public C { - // A的成员重复! -}; -``` - -**Solution**: Use virtual inheritance: - -```cpp -template -class A {}; - -template -class B : public virtual A {}; - -template -class C : public virtual A {}; - -class D : public B, public C { - // 只有一个A基类 -}; -``` - -### Pitfall 4: Calling virtual functions in constructors - -In CRTP, the base class is constructed first when the derived class is instantiated. Accessing derived class members at this point is undefined behavior: - -```cpp -template -class Base { -public: - Base() { - static_cast(this)->init(); // 危险! - } -}; - -class Derived : public Base { - std::vector data_; // 尚未构造 -public: - void init() { - data_.push_back(42); // 未定义行为 - } -}; -``` - -**Solution**: Provide two-phase initialization or use the Factory pattern: - -```cpp -template -class Base { -public: - void initialize() { - static_cast(this)->init(); // 安全 - } -}; - -class Derived : public Base { - std::vector data_; -public: - Derived() = default; - void init() { - data_.push_back(42); // 安全 - } -}; - -// 使用 -Derived d; -d.initialize(); // 在构造完成后调用 -``` - -### Pitfall 5: Template instantiation order - -Multiple CRTP classes depending on each other can cause instantiation order issues: - -```cpp -// A.h -template -class A { -public: - void method() { - static_cast(this)->b_method(); // B可能还未定义 - } -}; - -// B.h -template -class B { -public: - void a_method() { - static_cast(this)->method(); // 调用A - } - - void b_method() { - // B的实现 - } -}; - -// C.h -class C : public A, public B { - // 可能出现实例化顺序问题 -}; -``` - -**Solution**: Place the implementation in a cpp file or use explicit instantiation: - -```cpp -// 在cpp中提供实现 -template -void A::method_impl() { - static_cast(this)->b_method(); -} -``` - ------- - -## Summary - -CRTP is a powerful and elegant pattern in C++ that makes templates and inheritance work together to achieve: - -### Core takeaways - -1. **Static polymorphism**: Achieving polymorphism at compile-time, avoiding the overhead of virtual functions -2. **Code reuse**: The base class provides common logic, while the derived class provides specific implementations -3. **Type safety**: Compile-time checking of interface implementations -4. **Zero-overhead**: All calls can be inlined, delivering performance identical to hand-written code - -### Practical patterns - -| Pattern | Purpose | Example | -|---------|---------|---------| -| Singleton base class | Generic singleton implementation | `Singleton` | -| Object counter | Tracking object counts, detecting leaks | `ObjectCounter` | -| Polymorphic cloning | Returning a correctly typed clone | `Cloneable` | -| Mixin | Composing reusable functionality | `Printable`, `Comparable` | -| Interface injection | Compile-time interface checking | `Interface` | - -### Selection guidelines - -**Use CRTP when**: - -- Performance is a critical factor -- Types are determined at compile-time -- Inlining optimization is needed -- RAM is constrained (avoid vptr) - -**Use virtual functions when**: - -- Runtime polymorphism is needed -- Types are not determined at compile-time -- ABI stability is required -- Code size is more important than performance - -**In the next chapter**, we will explore **variadic templates**, learning how to handle an arbitrary number of template arguments and implement a type-safe callback system. diff --git a/documents/en/vol4-advanced/cpp-templates-index.md b/documents/en/vol4-advanced/cpp-templates-index.md deleted file mode 100644 index 9ada4a3df..000000000 --- a/documents/en/vol4-advanced/cpp-templates-index.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Modern C++ Template Tutorial (Planned) -description: '' -tags: -- cpp-modern -- host -- intermediate -difficulty: intermediate -platform: host -chapter: 12 -order: 9 ---- -# Modern C++ Template Tutorial (Planned) - -A four-volume series is currently being planned, so stay tuned! - -- **Volume One: Template Basics (C++11-14)** -- **Volume Two: Modern Template Techniques (C++17)** -- **Volume Three: Metaprogramming Essentials (C++20-23)** -- **Volume Four: Generic Design Patterns in Practice** diff --git a/documents/en/vol4-advanced/index.md b/documents/en/vol4-advanced/index.md index 60168292f..2cf86b387 100644 --- a/documents/en/vol4-advanced/index.md +++ b/documents/en/vol4-advanced/index.md @@ -17,18 +17,13 @@ This volume covers advanced C++20/23/26 features. ## Existing Articles (Pending Rewrite to Generic Content) -### Templates +### Template Programming (by C++ Standard) - Template Overview - Function Templates - Class Templates - Template Specialization - Non-Type Template Parameters - Template Arguments and Name Lookup - Template Friends and Barton-Nackman - Template Aliases and using - Templates and Inheritance CRTP + Template Basics (C++11-14) + Modern Template Techniques (C++17) + Metaprogramming Essentials (C++20-23) + Generic Design Patterns ### Coroutines @@ -41,8 +36,6 @@ This volume covers advanced C++20/23/26 features. ### Other - Modern C++ Template Tutorial (Planned) - if constexpr Spaceship Operator C++ Modules (MSVC) diff --git a/documents/en/vol4-advanced/vol1-basics-cpp11-14/index.md b/documents/en/vol4-advanced/vol1-basics-cpp11-14/index.md new file mode 100644 index 000000000..99d1e41a4 --- /dev/null +++ b/documents/en/vol4-advanced/vol1-basics-cpp11-14/index.md @@ -0,0 +1,23 @@ +--- +title: "Template Basics (C++11-14)" +description: "Core foundations of C++ template programming: from function templates to CRTP" +--- + +# Template Basics (C++11-14) + +> Status: Pending + +C++ templates are the core mechanism for generic programming. This section builds a complete foundational knowledge system covering function templates, class templates, specialization, non-type parameters, name lookup, friend injection, aliases, and CRTP. + +## Contents (Pending) + +1. Introduction to Templates +2. Function Templates +3. Class Templates +4. Template Specialization and Partial Specialization +5. Non-Type Template Parameters +6. Template Argument Dependent Name Lookup +7. Template Friends and Barton-Nackman +8. Template Aliases and Using Declarations +9. Templates and Inheritance (CRTP) +10. Project: fixed_vector<T, N> diff --git a/documents/en/vol4-advanced/vol2-modern-cpp17/index.md b/documents/en/vol4-advanced/vol2-modern-cpp17/index.md new file mode 100644 index 000000000..a538f8c0a --- /dev/null +++ b/documents/en/vol4-advanced/vol2-modern-cpp17/index.md @@ -0,0 +1,30 @@ +--- +title: "Modern Template Techniques (C++17)" +description: "C++17 modern template techniques: type traits, SFINAE, variadic templates, fold expressions, and more" +--- + +# Modern Template Techniques (C++17) + +> Status: Template articles pending, Ranges articles completed + +This section covers the core tools of modern C++ template programming, including type traits, SFINAE, if constexpr, variadic templates, fold expressions, perfect forwarding, constexpr, and CTAD. + +## Template Articles (Pending) + +1. Type Traits Deep Dive +2. SFINAE and enable_if +3. if constexpr: Compile-Time Branching +4. Variadic Templates +5. Fold Expressions +6. Perfect Forwarding +7. constexpr and Compile-Time Computation +8. CTAD +9. Project: Type-Safe any + +## Other Modern C++ Features + + + Designated Initializers + C++20 Ranges Basics and Views + Ranges Pipeline in Practice + diff --git a/documents/en/vol4-advanced/vol3-metaprogramming-cpp20-23/index.md b/documents/en/vol4-advanced/vol3-metaprogramming-cpp20-23/index.md new file mode 100644 index 000000000..c8d095b2f --- /dev/null +++ b/documents/en/vol4-advanced/vol3-metaprogramming-cpp20-23/index.md @@ -0,0 +1,22 @@ +--- +title: "Metaprogramming Essentials (C++20-23)" +description: "C++20/23 metaprogramming: Concepts, requires expressions, TMP techniques, compile-time strings" +--- + +# Metaprogramming Essentials (C++20-23) + +> Status: Pending + +This section explores modern C++ constraint mechanisms and advanced metaprogramming techniques, covering Concepts, requires expressions, TMP core techniques, compile-time strings, reflection basics, instantiation control, and exception safety. + +## Contents (Pending) + +1. Concepts in Detail +2. Constraining Templates with Concepts +3. Requires Expressions Deep Dive +4. TMP Core Techniques +5. Compile-Time String Processing +6. Reflection Metaprogramming Basics +7. Template Instantiation Control +8. Templates and Exception Safety +9. Project: mini-STL Algorithm Library diff --git a/documents/en/vol4-advanced/vol4-generics-patterns/index.md b/documents/en/vol4-advanced/vol4-generics-patterns/index.md new file mode 100644 index 000000000..6f159e305 --- /dev/null +++ b/documents/en/vol4-advanced/vol4-generics-patterns/index.md @@ -0,0 +1,24 @@ +--- +title: "Generic Design Patterns" +description: "Template design patterns: Policy-Based Design, Type Erasure, CRTP, DSL, and architectural applications" +--- + +# Generic Design Patterns + +> Status: Pending + +This section applies template techniques to real-world architecture design, covering Policy-Based Design, Type Erasure, NVI, Factory/Visitor/Singleton/Observer patterns with templates, Mixin composition, Tag Dispatching, and DSL construction. + +## Contents (Pending) + +1. Policy-Based Design +2. Type Erasure +3. Template Method Pattern and NVI +4. Factory Pattern with Templates +5. Visitor Pattern with Templates +6. Thread-Safe Singleton +7. Observer Pattern with Templates +8. Mixin and Compositional Design +9. Tag Dispatching and Type Dispatch +10. Templates and DSL +11. Project: Embedded Event System diff --git a/documents/en/vol8-domains/embedded/core-embedded-cpp-index.md b/documents/en/vol8-domains/embedded/core-embedded-cpp-index.md index 242571da0..4ac7fa9db 100644 --- a/documents/en/vol8-domains/embedded/core-embedded-cpp-index.md +++ b/documents/en/vol8-domains/embedded/core-embedded-cpp-index.md @@ -49,7 +49,7 @@ This is the table of contents for *Modern C++ for Embedded Systems*. Click any i ## Chapter 4 - Compile-Time Computation -- [if_constexpr](../../vol4-advanced/04-if-constexpr.md) +- [if constexpr](../../vol4-advanced/vol3-metaprogramming-cpp20-23/index.md) ## Chapter 5 - Memory Management Strategies @@ -88,12 +88,7 @@ This is the table of contents for *Modern C++ for Embedded Systems*. Click any i ## Chapter 12 - Template Basics -- [Template Overview](../../vol4-advanced/00-template-overview.md) -- [Function Templates in Detail](../../vol4-advanced/01-function-templates.md) -- [Class Templates in Detail](../../vol4-advanced/02-class-templates.md) -- [Template Specialization and Partial Specialization](../../vol4-advanced/03-template-specialization.md) -- [Non-Type Template Parameters](../../vol4-advanced/04-non-type-template-params.md) -- [Template Argument Dependent Name Lookup](../../vol4-advanced/05-template-args-and-name-lookup.md) -- [Template Friends and the Barton-Nackman Trick](../../vol4-advanced/06-template-friends-and-barton-nackman.md) -- [Template Aliases and Using Declarations](../../vol4-advanced/07-template-aliases-and-using.md) -- [Templates and Inheritance with CRTP](../../vol4-advanced/08-templates-and-inheritance-crtp.md) +- [Template Basics (C++11-14)](../../vol4-advanced/vol1-basics-cpp11-14/index.md) +- [Modern Template Techniques (C++17)](../../vol4-advanced/vol2-modern-cpp17/index.md) +- [Metaprogramming Essentials (C++20-23)](../../vol4-advanced/vol3-metaprogramming-cpp20-23/index.md) +- [Generic Design Patterns](../../vol4-advanced/vol4-generics-patterns/index.md) diff --git a/documents/vol4-advanced/00-template-overview.md b/documents/vol4-advanced/00-template-overview.md deleted file mode 100644 index 1dbb1176b..000000000 --- a/documents/vol4-advanced/00-template-overview.md +++ /dev/null @@ -1,370 +0,0 @@ ---- -chapter: 12 -cpp_standard: -- 11 -- 14 -- 17 -- 20 -description: 理解C++模板的核心概念与学习路径 -difficulty: beginner -order: 0 -platform: host -prerequisites: -- 'Chapter 2: 零开销抽象' -- 'Chapter 11: 类型推导基础' -reading_time_minutes: 10 -tags: -- cpp-modern -- host -- intermediate -title: 模板入门概述 ---- -# 嵌入式现代C++教程——模板入门概述 - -## 引言:为什么需要模板? - -想象这样一个场景:你正在为一个嵌入式项目编写通信协议栈,需要处理不同大小的数据包——8位、16位、32位、甚至64位的校验和计算。 - -用传统C风格,你可能会写出这样的代码: - -```cpp -uint8_t checksum8(const uint8_t* data, size_t len) { - uint8_t sum = 0; - for (size_t i = 0; i < len; ++i) { - sum += data[i]; - } - return sum; -} - -uint16_t checksum16(const uint16_t* data, size_t len) { - uint16_t sum = 0; - for (size_t i = 0; i < len; ++i) { - sum += data[i]; - } - return sum; -} - -uint32_t checksum32(const uint32_t* data, size_t len) { - uint32_t sum = 0; - for (size_t i = 0; i < len; ++i) { - sum += data[i]; - } - return sum; -} -``` - -三个函数,逻辑完全相同,只是类型不同。这不仅写起来烦,维护起来更烦——如果你要修改校验算法(比如加个溢出处理),得改三个地方。 - -这时候,C++模板就登场了。 - ------- - -## 什么是模板? - -**模板是C++的泛型编程机制**,它允许你编写与类型无关的代码,让编译器根据具体使用的类型生成对应的函数或类。 - -用模板重写上面的校验和函数: - -```cpp -template -T checksum(const T* data, size_t len) { - T sum = 0; - for (size_t i = 0; i < len; ++i) { - sum += data[i]; - } - return sum; -} - -// 使用时 -uint8_t data8[16] = { /* ... */ }; -auto sum8 = checksum(data8, 16); - -uint16_t data16[8] = { /* ... */ }; -auto sum16 = checksum(data16, 8); -``` - -一段代码,适用于所有类型。编译器会根据你调用的方式自动生成对应的函数版本——这个过程叫**模板实例化**。 - -> 一句话总结:**模板是编译期的代码生成器,它让你写出类型无关的代码,同时保持类型安全。** - ------- - -## 模板的核心价值 - -### 1. 类型安全 + 代码复用 - -C语言的宏(Macro)也能实现某种程度的"泛型",但它是文本替换,没有任何类型检查: - -```cpp -// C风格宏 - 不安全 -#define MAX(a, b) ((a) > (b) ? (a) : (b)) - -// 问题1:多次求值 -int x = 1; -int result = MAX(++x, 10); // x被递增两次!结果可能不是你想要的 - -// 问题2:类型不匹配 -double d = MAX(3.14, "hello"); // 编译器可能不报错,但行为未定义 -``` - -模板在编译期进行类型检查,既保证了安全,又实现了复用: - -```cpp -template -T max(const T& a, const T& b) { - return a > b ? a : b; -} - -int x = 1; -int result = max(++x, 10); // x只递增一次,行为确定 - -// double d = max(3.14, "hello"); // 编译错误!类型不匹配 -``` - -### 2. 零开销抽象 - -现代C++的核心理念之一:**抽象不应该带来运行时开销**。 - -模板在编译期展开,生成的代码与手写的优化版本没有区别。来看一个例子: - -```cpp -template -class FixedVector { -public: - T& operator[](std::size_t index) { - return data[index]; - } - // ... 其他成员 -private: - T data[N]; // 编译期确定大小,栈上分配 -}; - -FixedVector vec; // 编译为 int data[8],没有动态分配 -``` - -这比`std::vector`更适合嵌入式场景——无需堆分配,大小固定,内存布局完全可预测。 - -### 3. 编译期计算 - -模板是C++元编程的基础,允许在编译期完成复杂计算: - -```cpp -template -struct Factorial { - static constexpr std::size_t value = N * Factorial::value; -}; - -template<> -struct Factorial<0> { - static constexpr std::size_t value = 1; -}; - -// 编译期计算 -static_assert(Factorial<5>::value == 120); -``` - -这看起来像玩具,但在嵌入式里很有用——比如生成查找表、计算寄存器位掩码等。 - ------- - -## 嵌入式开发中的模板 - -### 模板在嵌入式的独特优势 - -| 优势 | 说明 | 实际应用 | -|------|------|----------| -| 编译期确定 | 无运行时分支 | 寄存器地址映射、协议解析 | -| 零堆分配 | 避免碎片 | 固定大小容器、对象池 | -| 类型安全 | 编译期错误检测 | 外设封装、单位系统 | -| 代码内联 | 减少函数调用开销 | 算法特化、热路径优化 | - -### 需要权衡的地方 - -模板也不是没有代价: - -- **代码膨胀**:每个模板实例化都会生成一份代码,Flash占用增加 -- **编译时间**:复杂的模板元编程会显著增加编译时间 -- **错误信息**:模板编译错误信息可能极其晦涩 -- **调试困难**:模板展开后的代码可能与源代码看起来很不一样 - -实用主义原则:**在关键路径上使用模板优化性能,在普通代码上保持简洁可读。** - ------- - -## 模板的基本类型 - -C++模板主要分为两大类: - -### 1. 函数模板 - -用于生成类型相关的函数: - -```cpp -template -T add(T a, T b) { - return a + b; -} - -// 或用 auto 返回类型推导 -template -auto multiply(T a, T b) -> decltype(a * b) { - return a * b; -} -``` - -### 2. 类模板 - -用于生成类型相关的类: - -```cpp -template -class Stack { -public: - void push(const T& item); - T pop(); - bool empty() const; -private: - std::vector data; -}; - -// 使用 -Stack int_stack; -Stack string_stack; -``` - -此外还有: - -- **成员模板**:类内部的模板函数 -- **变量模板**:C++14引入,用于变量级别的模板 -- **别名模板**:简化复杂类型名 - -这些将在后续章节详细介绍。 - ------- - -## 学习路线建议 - -模板学习曲线较陡,但遵循正确的路径可以事半功倍: - -### 第一阶段:掌握基础(1-2周) - -1. **理解模板实例化机制**:编译器如何从模板生成具体代码 -2. **函数模板**:参数推导、返回类型推导 -3. **类模板**:基本声明、成员定义、特化 -4. **实用技巧**:`auto`/`decltype`与模板的结合 - -### 第二阶段:深入类型系统(2-3周) - -1. **类型萃取**(Type Traits):``库的使用 -2. **SFINAE**:理解"替换失败并非错误" -3. **`std::enable_if`**:条件编译的技术 -4. **标签分发**(Tag Dispatching):编译期算法选择 - -### 第三阶段:现代模板技术(3-4周) - -1. **`constexpr`**:编译期计算 -2. **可变参数模板**:处理任意数量参数 -3. **折叠表达式**:简化参数包操作 -4. **`if constexpr`**:编译期条件分支 - -### 第四阶段:C++20 Concepts(1-2周) - -1. **Concepts定义**:约束模板参数 -2. **Requires表达式**:编写清晰的概念 -3. **缩写函数模板**:更简洁的语法 -4. **Concept重载**:更智能的重载决议 - -### 学习建议 - -- **动手实践**:每学一个概念就写代码验证,看生成的汇编 -- **阅读标准库**:`std::vector`、`std::algorithm`是最佳教材 -- **逐步深入**:不要一开始就陷入复杂的元编程 -- **实用主义**:在嵌入式中,能用简单方案解决的不要强行模板化 - ------- - -## 常见误区澄清 - -### 误区1:"模板会让代码变慢" - -**事实**:正确使用的模板代码与手写代码性能完全相同。编译器会对模板代码进行同样的优化。内联、常量传播、死代码消除等优化对模板代码完全有效。 - -### 误区2:"模板只适合库开发者" - -**事实**:模板是C++基础特性,理解它有助于更好地使用标准库、编写类型安全的代码。嵌入式开发者经常使用的`std::array`、`std::tuple`等都是模板。 - -### 误区3:"模板代码体积一定会膨胀" - -**事实**:膨胀程度取决于使用方式。通过共享基类、`extern template`显式实例化等技术可以有效控制。很多情况下,模板带来的编译期优化反而能减小最终代码。 - -### 误区4:"必须精通所有模板技巧" - -**事实**:掌握基础就足够应对80%的场景。复杂的元编程技巧只在特定场景下需要。 - ------- - -## 实战:第一个有用的模板 - -让我们用一个实用的例子结束本章——一个类型安全的位掩码工具: - -```cpp -template -struct BitMask { - static constexpr RegType mask = static_cast(1) << Bit; - - // 设置位 - static inline RegType set(RegType reg) { - return reg | mask; - } - - // 清除位 - static inline RegType clear(RegType reg) { - return reg & ~mask; - } - - // 切换位 - static inline RegType toggle(RegType reg) { - return reg ^ mask; - } - - // 测试位 - static inline bool is_set(RegType reg) { - return (reg & mask) != 0; - } -}; - -// 使用场景:GPIO配置 -using Pin5 = BitMask; - -uint32_t gpio_mode = 0; -gpio_mode = Pin5::set(gpio_mode); // 设置第5位 -if (Pin5::is_set(gpio_mode)) { - // 第5位已设置 -} -``` - -这段代码: - -- **类型安全**:编译期保证位索引有效 -- **零开销**:所有函数都会内联为单条指令 -- **自文档**:`Pin5::set()`比`gpio_mode |= (1 << 5)`更清晰 - ------- - -## 小结 - -模板是现代C++的核心特性,它: - -1. **提供类型安全的泛型编程**:避免宏的不安全性 -2. **实现零开销抽象**:编译期生成,与手写代码性能相同 -3. **支持编译期计算**:将运行时工作前置到编译期 -4. **是现代C++基础设施**:标准库、STL都建立在模板之上 - -对于嵌入式开发者,模板特别适合: - -- 编译期确定的配置 -- 类型安全的外设封装 -- 零堆分配的数据结构 -- 性能关键的算法特化 - -**下一章**,我们将深入探讨**函数模板**,学习模板参数推导、返回类型推导、重载决议等核心机制,并实现一个通用的`min/max/clamp`函数族。 diff --git a/documents/vol4-advanced/01-function-templates.md b/documents/vol4-advanced/01-function-templates.md deleted file mode 100644 index 52219a9b4..000000000 --- a/documents/vol4-advanced/01-function-templates.md +++ /dev/null @@ -1,854 +0,0 @@ ---- -chapter: 12 -cpp_standard: -- 11 -- 14 -- 17 -- 20 -description: 深入理解C++函数模板的推导规则与实战技巧 -difficulty: intermediate -order: 1 -platform: host -prerequisites: -- 'Chapter 12: 模板入门概述' -reading_time_minutes: 24 -tags: -- cpp-modern -- host -- intermediate -title: 函数模板详解 ---- -# 嵌入式现代C++教程——函数模板详解 - -函数模板是C++泛型编程的起点,它让你编写一套代码就能处理多种类型。但你真的理解编译器是如何推导模板参数的吗?为什么有时候推导会失败?`auto`和模板参数推导有什么区别? - -本章我们将深入探讨函数模板的内部机制,并实现一套类型安全的`min/max/clamp`函数族。 - ------- - -## 函数模板基础语法 - -### 基本形式 - -函数模板以`template<...>`开头,后跟函数声明: - -```cpp -template -T max(const T& a, const T& b) { - return a > b ? a : b; -} - -// 使用 -int x = max(5, 10); // T推导为int -double d = max(3.14, 2.71); // T推导为double -``` - -**关键点**: - -- `typename T`声明了一个类型模板参数`T` -- `typename`关键字可以用`class`替代(但推荐用`typename`) -- 编译器根据实参类型自动推导`T`的类型 - -### 多个模板参数 - -```cpp -template -auto add(T a, U b) -> decltype(a + b) { - return a + b; -} - -// 使用 -int x = 5; -double y = 3.14; -auto result = add(x, y); // 返回double -``` - -**注意**:`T`和`U`是独立推导的,可能推导出不同类型。 - -### 非类型模板参数 - -除了类型,模板参数还可以是编译期常量: - -```cpp -template -std::size_t array_size(T (&arr)[N]) { - return N; // 编译期获取数组大小 -} - -int data[42]; -std::size_t size = array_size(data); // 返回42,且是编译期常量 -``` - -这在嵌入式里特别有用——可以安全地获取数组大小而不会退化为指针。 - ------- - -## 模板参数推导规则 - -### 规则1:完美匹配原则 - -编译器会寻找"最匹配"的模板参数类型,不考虑隐式转换: - -```cpp -template -void process(T value); - -process(42); // T推导为int -process(3.14); // T推导为double -process('a'); // T推导为char - -// 但这不会工作: -process(42, 3.14); // 错误:只有一个T,无法同时匹配int和double -``` - -### 规则2:引用被忽略(默认情况) - -默认情况下,模板参数推导会忽略引用和顶层const: - -```cpp -template -void func(T arg); - -int x = 42; -const int& cref = x; - -func(x); // T推导为int -func(cref); // T推导为int(const和引用都被忽略) - -// 如果想保留引用和const: -template -void func_const(const T& arg); - -func_const(cref); // T推导为int,但参数类型是const int& -``` - -**记住**: - -- `T`推导的是"去掉引用和顶层const后的类型" -- `const T&`会保留引用语义 -- `T&&`是万能引用(稍后详述) - -### 规则3:数组退化为指针 - -```cpp -template -void func(T arg); - -int arr[10]; - -func(arr); // T推导为int*(数组退化为指针) - -// 如果想保留数组类型: -template -void func(T (&arr)[N]); - -int arr[10]; -func(arr); // T推导为int,N推导为10 -``` - -### 规则4:函数退化为函数指针 - -```cpp -template -void func(T arg); - -void some_func(int); - -func(some_func); // T推导为void(*)(int) - -// 保留函数类型: -template -void func_ref(T& arg); - -func_ref(some_func); // T推导为void(int) -``` - -### 实用推导表 - -| 实参类型 | `T` | `const T&` | `T&&` | -|----------|-----|-----------|-------| -| `int` | `int` | `int` | `int&&` | -| `const int` | `int` | `const int` | `const int&&` | -| `int&` | `int` | `const int&` | `int&` | -| `const int&` | `int` | `const int&` | `const int&` | -| `int&&` | `int` | `const int&` | `int&&` | - -**重要**:`T&&`只有当实参是右值时才推导为右值引用,否则推导为左值引用(引用折叠规则)。 - ------- - -## 尾随返回类型 - -C++11引入的尾随返回类型解决了"返回类型依赖参数类型"的问题: - -### 问题场景 - -```cpp -// ❌ 错误:T在返回类型时还未推导 -template -T add(T a, U b) { - return a + b; // 如果T是int,U是double,返回值截断 -} - -// ✅ 正确:使用尾随返回类型 -template -auto add(T a, U b) -> decltype(a + b) { - return a + b; // 返回decltype(a + b)的类型 -} -``` - -### C++14简化:返回类型推导 - -C++14允许直接使用`auto`作为返回类型,编译器自动推导: - -```cpp -template -auto add(T a, U b) { - return a + b; // 推导为decltype(a + b) -} -``` - -### 尾随返回类型的优势 - -1. **可以访问函数参数**: - - ```cpp - template - auto deref(T iter) -> decltype(*iter) { - return *iter; // 返回解引用结果的类型 - } - ``` - -2. **更适合复杂表达式**: - - ```cpp - template - auto multiply(T t, U u) -> decltype(t * u) { - return t * u; - } - ``` - -3. **更清晰的语法**(对于复杂返回类型): - - ```cpp - // 传统写法(难读) - std::map::iterator func(int x); - - // 尾随返回类型(清晰) - auto func(int x) -> std::map::iterator; - ``` - -### decltype(auto):完美转发返回值 - -C++14引入的`decltype(auto)`结合了`auto`的简洁和`decltype`的精确: - -```cpp -template -struct Container { - T data[100]; - - // auto:返回T(拷贝) - auto get1(std::size_t i) { - return data[i]; - } - - // decltype(auto):返回T&(引用) - decltype(auto) get2(std::size_t i) { - return (data[i]); // 注意括号! - } -}; -``` - -**关键区别**:括号会让`decltype`返回引用类型! - -```cpp -int x = 42; -decltype(x) a = 10; // int -decltype((x)) b = x; // int&(括号让表达式变成引用) -``` - ------- - -## 模板重载与特化 - -### 函数模板重载 - -函数模板可以与普通函数或其他模板重载: - -```cpp -// 模板版本 -template -T max(T a, T b) { - return a > b ? a : b; -} - -// 针对const char*的特化(实际上是重载) -const char* max(const char* a, const char* b) { - return std::strcmp(a, b) > 0 ? a : b; -} - -// 使用 -max(5, 10); // 调用模板,T=int -max("hello", "world"); // 调用const char*重载 -``` - -### 重载决议顺序 - -编译器按以下顺序选择: - -1. **完全匹配的普通函数** -2. **完全匹配的模板函数** -3. **需要转换的普通函数** -4. **需要转换的模板函数** - -```cpp -template -void func(T t); - -void func(int t); - -func(42); // 调用普通函数void func(int),优先级更高 -func(3.14); // 调用模板void func -``` - -### 函数模板"特化"的真相 - -**重要**:函数模板不支持真正的特化,只能通过重载实现! - -```cpp -// 主模板 -template -void process(T t) { - std::cout << "Generic: " << t << '\n'; -} - -// ❌ 这不是特化,是重载! -template<> -void process(int t) { - std::cout << "Int: " << t << '\n'; -} - -// ✅ 正确的"特化"方式:使用SFINAE或重载 -void process(int t) { - std::cout << "Int (overload): " << t << '\n'; -} -``` - -**建议**:函数模板优先使用重载而非特化,特化主要用于类模板。 - ------- - -## 万能引用与完美转发 - -### 万能引用(Universal Reference) - -当`T&&`出现在模板参数推导上下文中,它可能是左值引用或右值引用: - -```cpp -template -void wrapper(T&& arg) { // 万能引用 - // ... -} - -int x = 42; -wrapper(x); // T推导为int&,参数类型为int&(左值引用) -wrapper(42); // T推导为int,参数类型为int&&(右值引用) -``` - -**判断规则**:只有当`T`是推导出的模板参数,且类型为`T&&`时,才是万能引用。 - -```cpp -template -class MyClass { - void func1(T&& arg); // ❌ 不是万能引用(T是类模板参数) - void func2(auto&& arg); // ✅ 是万能引用(C++20) -}; - -void func(auto&& arg); // ✅ 是万能引用(C++20) -``` - -### 引用折叠规则 - -当模板参数推导涉及多层引用时,遵循引用折叠规则: - -| T | arg声明 | 最终类型 | -|---|---------|----------| -| `int` | `T&&` | `int&&` | -| `int&` | `T&&` | `int&` | -| `int&&` | `T&&` | `int&&` | - -简单记忆:**只有当两者都是右值引用时,结果才是右值引用,否则是左值引用。** - -### std::forward:保持值类别 - -```cpp -template -void wrapper(T&& arg) { - target(std::forward(arg)); // 完美转发 -} - -template -void target(T&& arg); - -int x = 42; -wrapper(x); // 转发为左值 -wrapper(42); // 转发为右值 -``` - -`std::forward`的实现原理: - -```cpp -template -T&& forward(std::remove_reference_t& arg) { - return static_cast(arg); -} - -// 当T=int&时:返回int& -// 当T=int时:返回int&& -``` - ------- - -## 实战:实现 min/max/clamp 函数族 - -让我们用学到的知识实现一套类型安全的函数族: - -### 基础版本 - -```cpp -template -constexpr T min(const T& a, const T& b) { - return a < b ? a : b; -} - -template -constexpr T max(const T& a, const T& b) { - return a > b ? a : b; -} - -template -constexpr T clamp(const T& value, const T& low, const T& high) { - return (value < low) ? low : (value > high) ? high : value; -} -``` - -### 初始化列表版本(处理多个参数) - -```cpp -template -constexpr T min(std::initializer_list list) { - T result = *list.begin(); - for (auto item : list) { - if (item < result) result = item; - } - return result; -} - -// 使用 -int m = min({5, 2, 8, 1, 9}); // 返回1 -``` - -### 比较器支持版本(类似std::版本) - -```cpp -template -constexpr const T& min(const T& a, const T& b, Compare comp) { - return comp(a, b) ? a : b; -} - -// 使用 -auto greater_min = min(5, 10, std::greater<>{}); // 返回10 -``` - -### 嵌入式优化版本 - -在嵌入式中,我们可能需要避免分支以提高性能: - -```cpp -template -constexpr T min_branchless(const T& a, const T& b) { - // 注意:这只对整数类型有效,且假设没有溢出 - return a < b ? a : b; // 编译器通常能优化为cmov指令 -} - -// 或者使用位运算(仅无符号整数) -template -constexpr T min_bitwise(const T& a, const T& b) { - static_assert(std::is_unsigned_v, "Only for unsigned types"); - return b ^ ((a ^ b) & -(a < b)); -} - -// 使用场景:信号处理、实时控制 -uint16_t sample = min_bitwise(raw_sample, threshold); -``` - -### 类型安全的clamp(带编译期检查) - -```cpp -template -constexpr T clamp(const T& value, const T& low, const T& high) { - static_assert(low <= high, "clamp: low must be <= high"); - return (value < low) ? low : (value > high) ? high : value; -} - -// 编译期检查 -constexpr auto result = clamp(5, 0, 10); // OK -// constexpr auto error = clamp(5, 10, 0); // 编译错误! -``` - -### 完整实现(综合版) - -```cpp -template -constexpr const T& clamp(const T& value, const T& low, const T& high) { - static_assert(low <= high, "clamp: low must be <= high"); - return (value < low) ? low : (value > high) ? high : value; -} - -// 版本2:支持自定义比较器 -template -constexpr const T& clamp(const T& value, const T& low, const T& high, Compare comp) { - return comp(value, low) ? low : comp(high, value) ? high : value; -} - -// 版本3:返回值而非引用(避免临时对象问题) -template -constexpr T clamp_value(T value, T low, T high) { - return (value < low) ? low : (value > high) ? high : value; -} -``` - -### 使用示例 - -```cpp -// 传感器数值限制 -int16_t sensor_value = read_sensor(); -int16_t limited = clamp(sensor_value, -1000, 1000); - -// PWM占空比限制 -uint8_t duty = clamp(calculated_duty, 0, 255); - -// 浮点数限制 -float frequency = clamp(target_freq, 1000.0f, 5000.0f); -``` - ------- - -## 嵌入式贴士:避免代码膨胀 - -模板在嵌入式开发中的主要问题是代码膨胀。每个模板实例化都会生成一份代码,Flash占用快速增长。 - -### 技巧1:使用公共基类 - -```cpp -// ❌ 代码膨胀:每个类型都生成完整代码 -template -class Buffer { - T data[100]; - void clear() { /* 100行代码 */ } - void process() { /* 50行代码 */ } -}; - -// ✅ 优化:将类型无关部分提取到基类 -class BufferBase { -protected: - void clear_impl(void* data, std::size_t size); - void process_impl(void* data, std::size_t size); -}; - -template -class Buffer : private BufferBase { - T data[100]; -public: - void clear() { clear_impl(data, sizeof(data)); } - void process() { process_impl(data, sizeof(data)); } -}; -``` - -### 技巧2:extern template显式实例化 - -C++11允许在头文件中声明模板,在源文件中显式实例化: - -```cpp -// header.h -template -void heavy_function(T t); - -// header.tpp(实现) -template -void heavy_function(T t) { - /* 大量代码 */ -} - -// header.cpp(显式实例化) -extern template void heavy_function; -extern template void heavy_function; -extern template void heavy_function; - -template void heavy_function; -template void heavy_function; -template void heavy_function; -``` - -这样,其他翻译单元不会重复实例化这些类型。 - -### 技巧3:类型擦除 - -对于不需要编译期类型信息的场景,使用类型擦除: - -```cpp -// ❌ 每种传感器类型都生成一份代码 -template -void process_sensor(Sensor& s) { - s.read(); - s.calibrate(); - // ... 大量代码 -} - -// ✅ 使用接口+虚函数 -class ISensor { -public: - virtual void read() = 0; - virtual void calibrate() = 0; - // ... -}; - -void process_sensor(ISensor& s) { - s.read(); - s.calibrate(); - // 只有一份代码 -} -``` - -### 技巧4:限制模板特化数量 - -```cpp -// ❌ 对每种配置都生成代码 -template -class Config; - -// ✅ 只对常用配置特化 -extern template class Config; -extern template class Config; -extern template class Config; -``` - -### 技巧5:使用`constexpr`+类型选择 - -```cpp -// 只在编译期生成需要的版本 -template -class FixedBuffer { - static_assert(Size <= 256, "Buffer too large"); - // ... 编译期确定大小 -}; - -// 而不是运行时分支 -void buffer(size_t size); // 需要处理所有大小 -``` - -### 代码膨胀检测工具 - -- **编译器输出**:查看生成的汇编或目标文件大小 -- **map文件**:分析符号表,找出重复代码 -- **nm/size命令**:比较不同配置的二进制大小 - -```bash -# 查看符号大小 -nm --size-sort output.elf | head -20 - -# 查看段大小 -size output.elf -``` - ------- - -## 常见陷阱与解决方案 - -### 陷阱1:推导失败 - -```cpp -template -void func(T a, T b); - -func(42, 3.14); // ❌ 错误:T无法同时匹配int和double - -// 解决方案1:显式指定 -func(42, 3.14); - -// 解决方案2:两个模板参数 -template -void func(T a, U b); - -// 解决方案3:使用通用类型 -template -void func(T a, decltype(T{} + b) b); -``` - -### 陷阱2:返回引用到临时对象 - -```cpp -template -decltype(auto) get_first(const T& container) { - return container[0]; // ❌ 返回临时对象的引用! -} - -// ✅ 正确做法 -template -decltype(auto) get_first(T& container) { - return container[0]; // 返回引用 -} -``` - -### 陷阱3:`auto`返回类型丢失引用 - -```cpp -template -auto get_element(T& container, std::size_t index) { - return container[index]; // ❌ 返回拷贝而非引用 -} - -// ✅ 使用decltype(auto) -template -decltype(auto) get_element(T& container, std::size_t index) { - return container[index]; // ✅ 返回引用 -} -``` - -### 陷阱4:SFINAE与硬错误混淆 - -```cpp -template -auto func(T t) -> decltype(t.some_method()) { - return t.some_method(); -} - -func(42); // ❌ 硬错误:int没有some_method - // ✅ SFINAE场景:只是移除候选函数 -``` - -正确的SFINAE需要`std::enable_if`或C++17的`if constexpr`: - -```cpp -template -std::enable_if_t, T> func(T t) { - return t + 1; -} - -// 或C++17风格 -template -auto func(T t) { - if constexpr (std::is_integral_v) { - return t + 1; - } else { - return t; - } -} -``` - ------- - -## C++14/17/20的新特性 - -### C++14:函数返回类型推导 - -```cpp -// C++11需要尾随返回类型 -template -auto add(T t, U u) -> decltype(t + u) { - return t + u; -} - -// C++14可以直接用auto -template -auto add(T t, U u) { - return t + u; -} -``` - -### C++17:类模板参数推导(CTAD) - -虽然主要用于类模板,但也影响函数模板: - -```cpp -template -void process(std::vector vec); - -std::vector v{1, 2, 3}; // C++17 CTAD -process(v); // T自动推导为int -``` - -### C++17:if constexpr - -简化模板内的条件编译: - -```cpp -template -void process(T t) { - if constexpr (std::is_integral_v) { - // 整数分支 - } else if constexpr (std::is_floating_point_v) { - // 浮点分支 - } else { - // 其他分支 - } -} -``` - -### C++20:约束与缩写函数模板 - -```cpp -// 传统写法 -template -void func(T t) { - static_assert(std::is_integral_v); -} - -// C++20 Concepts -template -void func(T t); // 更清晰的约束 - -// 缩写函数模板 -void func(std::integral auto t); // 等价于上面 -``` - -### C++20:模板语法改进 - -```cpp -// 类模板参数可以作为类型名 -template -struct Container { - T value; - Container(T value) : value(value) {} - - // C++20之前 - // Container operator+(const Container& other); - - // C++20:省略 - Container operator+(const Container& other); -}; -``` - ------- - -## 小结 - -函数模板是C++泛型编程的基础: - -| 特性 | 说明 | 使用场景 | -|------|------|----------| -| 模板参数推导 | 编译器自动推导T的类型 | 简化函数调用 | -| 尾随返回类型 | 返回类型依赖参数类型 | 复杂类型计算 | -| 万能引用 | T&&可以是左值或右值引用 | 完美转发 | -| 完美转发 | std::forward保持值类别 | 转发函数 | -| 模板重载 | 与普通函数共存 | 类型特化处理 | - -**实践建议**: - -1. **优先使用`auto`返回类型**(C++14+),除非需要精确控制 -2. **需要转发时使用`decltype(auto)`**,保留引用语义 -3. **完美转发使用`T&&`+`std::forward`**,不要直接使用`T&&` -4. **函数特化用重载实现**,真正的特化是给类模板用的 -5. **嵌入式中注意代码膨胀**,使用显式实例化或类型擦除控制 - -**下一章**,我们将探讨**类模板**,学习如何实现泛型容器、理解模板成员函数的特殊规则,并实现一个固定容量的环形缓冲区。 diff --git a/documents/vol4-advanced/02-class-templates.md b/documents/vol4-advanced/02-class-templates.md deleted file mode 100644 index d5efd57a9..000000000 --- a/documents/vol4-advanced/02-class-templates.md +++ /dev/null @@ -1,1913 +0,0 @@ ---- -chapter: 12 -cpp_standard: -- 11 -- 14 -- 17 -- 20 -description: 深入理解C++类模板的声明、实例化与嵌入式应用 -difficulty: intermediate -order: 2 -platform: host -prerequisites: -- 'Chapter 12.1: 函数模板详解' -reading_time_minutes: 51 -tags: -- cpp-modern -- host -- intermediate -title: 类模板详解 ---- -# 嵌入式现代C++教程——类模板详解 - -类模板是C++泛型编程的核心机制,它允许你编写与类型无关的类定义。标准库中的容器(如`std::vector`、`std::array`)、智能指针(如`std::unique_ptr`)、元组(`std::tuple`)都是类模板的典型应用。 - -对于嵌入式开发者来说,类模板尤其重要——它让你能在编译期确定类型和大小,避免堆分配,实现零开销的抽象。 - -本章将深入探讨类模板的声明与定义、成员函数模板、虚函数与模板的限制,并最终实现一个实用的固定容量环形缓冲区。 - ------- - -## 类模板基础语法 - -### 基本声明形式 - -类模板以`template<...>`开头,后跟类定义: - -```cpp -template -class Stack { -private: - T data[100]; - std::size_t top_index = 0; -public: - void push(const T& item) { - data[top_index++] = item; - } - - T pop() { - return data[--top_index]; - } - - bool empty() const { - return top_index == 0; - } -}; - -// 使用 -Stack int_stack; -Stack float_stack; - -int_stack.push(42); -float_stack.push(3.14f); -``` - -**关键点**: - -- `template`声明了一个类型模板参数`T` -- 使用类模板时必须显式指定模板参数:`Stack` -- 每个不同的模板参数组合都会生成独立的类 - -### 多个类型参数 - -```cpp -template -class StaticMap { -private: - struct Entry { - Key key; - Value value; - bool occupied = false; - }; - Entry entries[Capacity]; -public: - bool insert(const Key& key, const Value& value) { - for (auto& entry : entries) { - if (!entry.occupied) { - entry.key = key; - entry.value = value; - entry.occupied = true; - return true; - } - } - return false; // 已满 - } - - bool find(const Key& key, Value& out_value) const { - for (const auto& entry : entries) { - if (entry.occupied && entry.key == key) { - out_value = entry.value; - return true; - } - } - return false; - } -}; - -// 使用 -StaticMap sensor_readings; -sensor_readings.insert("temperature", 25); -sensor_readings.insert("humidity", 60); -``` - -这种多参数设计在嵌入式中非常实用,可以在编译期确定容器容量。 - -### 非类型模板参数 - -除了类型,模板参数还可以是编译期常量: - -```cpp -template -class FixedArray { -private: - T data_[N]; -public: - constexpr std::size_t size() const { return N; } - - T& operator[](std::size_t index) { - return data_[index]; - } - - const T& operator[](std::size_t index) const { - return data_[index]; - } -}; - -// 使用 -FixedArray channel_values; -FixedArray adc_buffer; -``` - -**非类型模板参数的限制**: - -- 必须是编译期常量 -- 类型限制:整型、枚举、指针、引用、`std::nullptr_t`、C++20的浮点型和字面类型 -- 对于指针/引用,必须是静态存储期的对象地址 - -### 模板参数默认值 - -类模板可以为模板参数指定默认值: - -```cpp -template -class RingBuffer { -private: - T data_[N]; - std::size_t read_pos_ = 0; - std::size_t write_pos_ = 0; -public: - // ... -}; - -// 使用 -RingBuffer buffer1; // 容量32 -RingBuffer buffer2; // 容量16(使用默认值) -``` - -**默认值规则**: - -- 一旦为某个参数设置默认值,其后的所有参数都必须有默认值 -- 函数模板的模板参数默认值(C++11引入)规则相同 - -```cpp -template // OK -class Array; - -template // ERROR: T没有默认值 -class Array; -``` - ------- - -## 模板参数的命名惯例 - -### typename vs class - -在模板参数声明中,`typename`和`class`可以互换: - -```cpp -template class Container1; // 推荐 -template class Container2; // 也可以 - -template class Both; // 混用 -``` - -**推荐使用`typename`**,原因: - -- 语义更清晰(是类型,不一定是类) -- 与`typename`在模板内部的使用一致 - -### 常见命名惯例 - -| 惯例 | 含义 | 示例 | -|------|------|------| -| `T` | 单个类型参数 | `template` | -| `T, U` | 多个类型参数 | `template` | -| `Key`, `Value` | 容器相关 | `template` | -| `N`, `Size` | 大小参数 | `template` | -| `Fn` | 函数/可调用对象 | `template` | -| `IntType` | 整数类型 | `template` | - -```cpp -// 良好的命名示例 -template -class StaticMap { }; - -template -class AtomicInt { }; - -template -class Span { }; -``` - ------- - -## 成员函数模板 - -类模板的成员函数本身也可以是模板: - -### 成员函数模板基础 - -```cpp -template -class Container { -private: - T data_; - -public: - // 普通构造函数 - explicit Container(const T& data) : data_(data) {} - - // 成员函数模板 - template - U convert_to() const { - return static_cast(data_); - } - - // 成员函数模板(多个模板参数) - template - U transform_with(Fn fn) const { - return fn(data_); - } -}; - -// 使用 -Container c(42); -double d = c.convert_to(); // U推导为double -std::string s = c.transform_with( - [](int x) { return std::to_string(x); } -); -``` - -### 成员函数模板的特化 - -成员函数模板可以针对特定类型进行特化: - -```cpp -template -class Printer { -public: - void print(const T& value) { - std::cout << value << '\n'; - } - - // 成员函数模板特化(C++17之前需要全特化) - template - void print_special(const U& value); - - // 针对bool的特化 - template<> - void print_special(bool value) { - std::cout << (value ? "true" : "false") << '\n'; - } -}; - -// 使用 -Printer p; -p.print(42); -p.print_special(true); // 输出 "true" -``` - -**注意**:成员函数模板不支持偏特化,只能全特化。 - -### 构造函数模板 - -构造函数模板常用于类型转换: - -```cpp -template -class Box { -private: - T value_; - -public: - // 普通构造函数 - explicit Box(const T& value) : value_(value) {} - - // 构造函数模板(允许从其他类型构造) - template - explicit Box(const Box& other) : value_(other.get()) {} - - T get() const { return value_; } -}; - -// 使用 -Box int_box(42); -Box double_box = int_box; // 使用构造函数模板 -``` - -**重要**:构造函数模板不会阻止编译器生成拷贝构造函数! - -```cpp -Box box1(42); -Box box2 = box1; // 调用拷贝构造函数,而非模板构造函数 -``` - -### 赋值运算符模板 - -```cpp -template -class Optional { -private: - T* value_ = nullptr; - -public: - // 赋值运算符模板 - template - Optional& operator=(const Optional& other) { - if (other.value_) { - if (!value_) value_ = new T; - *value_ = static_cast(*other.value_); - } else { - delete value_; - value_ = nullptr; - } - return *this; - } - - ~Optional() { delete value_; } -}; -``` - ------- - -## 虚函数与模板的限制 - -### 规则1:虚函数不能是模板函数 - -```cpp -class Base { -public: - // ❌ 错误:虚函数不能是模板 - template - virtual void process(T value) {} - - // ✅ 正确:普通虚函数 - virtual void process(int value) {} -}; -``` - -**原因**:虚函数通过虚函数表(vtable)实现,每个虚函数在vtable中占据固定位置。模板函数只有在实例化后才存在,编译期无法确定vtable布局。 - -### 规则2:类模板可以有虚函数 - -```cpp -template -class Sensor { -public: - virtual ~Sensor() = default; - virtual T read() = 0; - virtual void calibrate() = 0; -}; - -// 使用 -class TemperatureSensor : public Sensor { -public: - float read() override { return 25.0f; } - void calibrate() override { } -}; -``` - -### 规则3:虚成员函数不能有默认模板参数 - -```cpp -template -class Base { -public: - // ❌ 错误:虚函数模板参数默认值 - template - virtual void func(U arg) = 0; -}; -``` - -### 设计建议:使用类型擦除替代虚函数模板 - -如果需要"虚函数模板"的效果,可以使用类型擦除: - -```cpp -// 方案1:使用std::function(动态类型擦除) -class Processor { -private: - std::function int_processor_; - std::function double_processor_; - std::function string_processor_; - -public: - template - void set_processor() { - if constexpr (std::is_same_v) { - int_processor_ = [](int x) { std::cout << "int: " << x << '\n'; }; - } else if constexpr (std::is_same_v) { - double_processor_ = [](double x) { std::cout << "double: " << x << '\n'; }; - } - } - - template - void process(T value) { - if constexpr (std::is_same_v) { - if (int_processor_) int_processor_(value); - } else if constexpr (std::is_same_v) { - if (double_processor_) double_processor_(value); - } - } -}; - -// 方案2:使用variant(C++17静态多态) -#include - -template -struct Visitor : Ts... { - using Ts::operator()...; -}; - -template -Visitor(Ts...) -> Visitor; - -class DynamicProcessor { -private: - using Value = std::variant; - std::vector data_; - -public: - template - void add(T value) { - data_.push_back(value); - } - - void process_all() { - Visitor visitor{ - [](int x) { std::cout << "int: " << x << '\n'; }, - [](double x) { std::cout << "double: " << x << '\n'; }, - [](const std::string& s) { std::cout << "string: " << s << '\n'; } - }; - for (auto& value : data_) { - std::visit(visitor, value); - } - } -}; -``` - -### 虚函数与模板的性能权衡 - -| 特性 | 虚函数 | 模板 | -|------|--------|------| -| 多态类型 | 运行时 | 编译期 | -| 代码大小 | 小(共享代码) | 大(每个实例生成代码) | -| 调用开销 | 间接调用(vtable查找) | 直接调用(可内联) | -| 类型安全 | 基类接口限制 | 完全类型安全 | -| 二进制兼容 | 好(ABI稳定) | 差(重新编译) | - -**嵌入式建议**: - -- 性能关键路径:使用模板(编译期多态) -- 运行时配置/插件架构:使用虚函数(运行时多态) -- 两者结合:模板生成具体类型,通过基类接口统一管理 - ------- - -## 类模板的实例化 - -### 隐式实例化 - -当使用类模板时,编译器自动实例化需要的成员函数: - -```cpp -template -class Counter { -private: - T count_ = 0; - -public: - void increment() { ++count_; } - void decrement() { --count_; } - T get() const { return count_; } -}; - -// 只使用了 increment 和 get -Counter c; -c.increment(); -auto value = c.get(); - -// decrement 不会被实例化(未被使用) -// 这意味着即使 decrement 有编译错误,也不会触发 -``` - -**实用技巧**:可以用这个特性实现条件编译: - -```cpp -template -class Container { -public: - void process() { - if constexpr (std::is_integral_v) { - // 整数特有操作 - } else { - // 其他类型操作 - } - } - - // 这个函数只对整数类型有效 - template - std::enable_if_t, U> get_bitmask() { - return static_cast(0xFF); - } -}; -``` - -### 显式实例化 - -可以显式告诉编译器实例化特定类型: - -```cpp -// header.h -template -class Vector { - // ... 大量代码 -}; - -// header.cpp -// 显式实例化(在cpp文件中) -template class Vector; -template class Vector; -template class Vector; - -// 使用时,这些类型的定义已经存在,不需要重新实例化 -``` - -**好处**: - -- 减少编译时间 -- 隐藏实现细节(模板实现放在cpp文件中) -- 控制代码膨胀 - -### extern template声明(C++11) - -告诉编译器"这个模板别处已经实例化了": - -```cpp -// my_vector.h -template -class Vector { - // ... 完整定义 -}; - -// my_vector.cpp -template class Vector; -template class Vector; - -// main.cpp -extern template class Vector; // 声明:Vector在别处实例化 -extern template class Vector; - -void use_vector() { - Vector v; // 使用已实例化的版本 -} -``` - -**使用场景**:大型项目中减少编译时间和代码膨胀。 - ------- - -## 实战:固定容量环形缓冲区 - -让我们实现一个实用的、固定容量的环形缓冲区——嵌入式中常用的数据结构,用于串口通信、传感器数据采集等场景。 - -### 设计目标 - -1. **零堆分配**:所有存储在栈上或静态存储区 -2. **编译期确定大小**:使用非类型模板参数 -3. **类型安全**:支持任意元素类型 -4. **线程安全(可选)**:提供原子操作的配置选项 -5. **优雅处理满/空状态**:区分满和空 - -### 基础实现 - -```cpp -#include -#include -#include - -template -class RingBuffer { -private: - std::array data_; - std::size_t read_pos_ = 0; - std::size_t write_pos_ = 0; - bool full_ = false; - -public: - static constexpr std::size_t capacity = N; - - // 默认构造 - RingBuffer() = default; - - // 检查状态 - bool empty() const { - return !full_ && (read_pos_ == write_pos_); - } - - bool full() const { - return full_; - } - - std::size_t size() const { - if (full_) return N; - if (write_pos_ >= read_pos_) return write_pos_ - read_pos_; - return N + write_pos_ - read_pos_; - } - - // 写入元素 - bool push(const T& item) { - if (full_) { - return false; // 缓冲区满 - } - - data_[write_pos_] = item; - write_pos_ = (write_pos_ + 1) % N; - - if (write_pos_ == read_pos_) { - full_ = true; - } - - return true; - } - - bool push(T&& item) { - if (full_) { - return false; - } - - data_[write_pos_] = std::move(item); - write_pos_ = (write_pos_ + 1) % N; - - if (write_pos_ == read_pos_) { - full_ = true; - } - - return true; - } - - // 读取元素 - bool pop(T& item) { - if (empty()) { - return false; // 缓冲区空 - } - - item = data_[read_pos_]; - read_pos_ = (read_pos_ + 1) % N; - full_ = false; - - return true; - } - - // 查看首元素(不移除) - bool peek(T& item) const { - if (empty()) { - return false; - } - - item = data_[read_pos_]; - return true; - } - - // 清空缓冲区 - void clear() { - read_pos_ = 0; - write_pos_ = 0; - full_ = false; - } -}; -``` - -### 线程安全版本 - -```cpp -template -class RingBufferTS; - -// 非线程安全版本 -template -class RingBufferTS : public RingBuffer { - using Base = RingBuffer; -public: - using Base::Base; -}; - -// 线程安全版本 -template -class RingBufferTS { -private: - std::array data_; - std::atomic read_pos_{0}; - std::atomic write_pos_{0}; - std::atomic full_{false}; - -public: - static constexpr std::size_t capacity = N; - - bool empty() const { - return !full_.load(std::memory_order_acquire) && - (read_pos_.load(std::memory_order_acquire) == - write_pos_.load(std::memory_order_acquire)); - } - - bool full() const { - return full_.load(std::memory_order_acquire); - } - - std::size_t size() const { - const auto rp = read_pos_.load(std::memory_order_acquire); - const auto wp = write_pos_.load(std::memory_order_acquire); - const bool f = full_.load(std::memory_order_acquire); - - if (f) return N; - if (wp >= rp) return wp - rp; - return N + wp - rp; - } - - bool push(const T& item) { - const auto wp = write_pos_.load(std::memory_order_relaxed); - const auto next_wp = (wp + 1) % N; - const auto rp = read_pos_.load(std::memory_order_acquire); - - if (next_wp == rp && full_.load(std::memory_order_acquire)) { - return false; // 满 - } - - data_[wp] = item; - write_pos_.store(next_wp, std::memory_order_release); - - if (next_wp == rp) { - full_.store(true, std::memory_order_release); - } - - return true; - } - - bool pop(T& item) { - const auto rp = read_pos_.load(std::memory_order_relaxed); - const auto wp = write_pos_.load(std::memory_order_acquire); - - if (rp == wp && !full_.load(std::memory_order_acquire)) { - return false; // 空 - } - - item = data_[rp]; - const auto next_rp = (rp + 1) % N; - read_pos_.store(next_rp, std::memory_order_release); - full_.store(false, std::memory_order_release); - - return true; - } -}; -``` - -### 嵌入式优化版本 - -针对资源受限的嵌入式环境,我们可以进一步优化: - -```cpp -template -class CompactRingBuffer { -private: - // 使用位域优化:假设容量不超过255 - static_assert(N <= 256, "Capacity too large for compact storage"); - - T data_[N]; - uint8_t read_pos_ = 0; - uint8_t write_pos_ = 0; - uint8_t count_ = 0; // 使用计数而非full标志 - -public: - static constexpr std::size_t capacity = N; - - // 内联所有函数(嵌入式常用) - [[gnu::always_inline]] bool empty() const { - return count_ == 0; - } - - [[gnu::always_inline]] bool full() const { - return count_ == N; - } - - [[gnu::always_inline]] std::size_t size() const { - return count_; - } - - // 批量写入(DMA友好) - std::size_t push_batch(const T* items, std::size_t count) { - std::size_t written = 0; - - for (std::size_t i = 0; i < count && !full(); ++i) { - data_[write_pos_] = items[i]; - write_pos_ = static_cast((write_pos_ + 1) % N); - ++count_; - ++written; - } - - return written; - } - - // 批量读取(DMA友好) - std::size_t pop_batch(T* items, std::size_t count) { - std::size_t read = 0; - - for (std::size_t i = 0; i < count && !empty(); ++i) { - items[i] = data_[read_pos_]; - read_pos_ = static_cast((read_pos_ + 1) % N); - --count_; - ++read; - } - - return read; - } - - // 直接访问底层存储(用于DMA传输) - T* data() { return data_; } - const T* data() const { return data_; } - - // 获取连续空间大小(用于DMA) - std::size_t contiguous_write_space() const { - if (write_pos_ >= read_pos_) { - return N - write_pos_; - } - return read_pos_ - write_pos_ - 1; - } - - std::size_t contiguous_read_space() const { - if (read_pos_ > write_pos_) { - return N - read_pos_; - } - return write_pos_ - read_pos_; - } -}; -``` - -### 完整实现示例 - -::: details 点击展开完整的RingBuffer实现(含迭代器支持) - -```cpp -#include -#include -#include -#include - -template -class RingBuffer { -private: - std::array data_; - std::size_t read_pos_ = 0; - std::size_t write_pos_ = 0; - bool full_ = false; - -public: - static constexpr std::size_t capacity = N; - - // 类型定义 - using value_type = T; - using size_type = std::size_t; - using reference = T&; - using const_reference = const T&; - - // 构造函数 - RingBuffer() = default; - - // 迭代器支持 - class iterator { - private: - RingBuffer* buffer_; - std::size_t pos_; - bool ended_; - - public: - using iterator_category = std::forward_iterator_tag; - using value_type = T; - using difference_type = std::ptrdiff_t; - using pointer = T*; - using reference = T&; - - iterator(RingBuffer* buffer, std::size_t pos, bool ended) - : buffer_(buffer), pos_(pos), ended_(ended) {} - - reference operator*() { return buffer_->data_[pos_]; } - pointer operator->() { return &buffer_->data_[pos_]; } - - iterator& operator++() { - if (pos_ == buffer_->write_pos_) { - ended_ = true; - } else { - pos_ = (pos_ + 1) % N; - } - return *this; - } - - iterator operator++(int) { - iterator tmp = *this; - ++(*this); - return tmp; - } - - friend bool operator==(const iterator& a, const iterator& b) { - return a.buffer_ == b.buffer_ && - a.pos_ == b.pos_ && - a.ended_ == b.ended_; - } - - friend bool operator!=(const iterator& a, const iterator& b) { - return !(a == b); - } - }; - - class const_iterator { - private: - const RingBuffer* buffer_; - std::size_t pos_; - bool ended_; - - public: - using iterator_category = std::forward_iterator_tag; - using value_type = const T; - using difference_type = std::ptrdiff_t; - using pointer = const T*; - using reference = const T&; - - const_iterator(const RingBuffer* buffer, std::size_t pos, bool ended) - : buffer_(buffer), pos_(pos), ended_(ended) {} - - reference operator*() const { return buffer_->data_[pos_]; } - pointer operator->() const { return &buffer_->data_[pos_]; } - - const_iterator& operator++() { - if (pos_ == buffer_->write_pos_) { - ended_ = true; - } else { - pos_ = (pos_ + 1) % N; - } - return *this; - } - - const_iterator operator++(int) { - const_iterator tmp = *this; - ++(*this); - return tmp; - } - - friend bool operator==(const const_iterator& a, const const_iterator& b) { - return a.buffer_ == b.buffer_ && - a.pos_ == b.pos_ && - a.ended_ == b.ended_; - } - - friend bool operator!=(const const_iterator& a, const const_iterator& b) { - return !(a == b); - } - }; - - iterator begin() { return empty() ? end() : iterator(this, read_pos_, false); } - iterator end() { return iterator(this, write_pos_, true); } - const_iterator begin() const { return cbegin(); } - const_iterator end() const { return cend(); } - const_iterator cbegin() const { - return empty() ? cend() : const_iterator(this, read_pos_, false); - } - const_iterator cend() const { return const_iterator(this, write_pos_, true); } - - // 状态查询 - bool empty() const { return !full_ && (read_pos_ == write_pos_); } - bool full() const { return full_; } - std::size_t size() const { - if (full_) return N; - if (write_pos_ >= read_pos_) return write_pos_ - read_pos_; - return N + write_pos_ - read_pos_; - } - static constexpr std::size_t capacity() { return N; } - - // 元素访问 - bool push(const T& item) { - if (full_) return false; - data_[write_pos_] = item; - advance_write(); - return true; - } - - bool push(T&& item) { - if (full_) return false; - data_[write_pos_] = std::move(item); - advance_write(); - return true; - } - - template - bool emplace(Args&&... args) { - if (full_) return false; - data_[write_pos_] = T(std::forward(args)...); - advance_write(); - return true; - } - - bool pop(T& item) { - if (empty()) return false; - item = std::move(data_[read_pos_]); - advance_read(); - return true; - } - - bool peek(T& item) const { - if (empty()) return false; - item = data_[read_pos_]; - return true; - } - - // 批量操作 - template - std::size_t push_range(InputIt first, InputIt last) { - std::size_t count = 0; - for (auto it = first; it != last && !full(); ++it) { - data_[write_pos_] = *it; - advance_write(); - ++count; - } - return count; - } - - // 清空 - void clear() { - read_pos_ = 0; - write_pos_ = 0; - full_ = false; - } - -private: - void advance_write() { - write_pos_ = (write_pos_ + 1) % N; - if (write_pos_ == read_pos_) { - full_ = true; - } - } - - void advance_read() { - read_pos_ = (read_pos_ + 1) % N; - full_ = false; - } -}; - -``` - -::: - -### 使用示例 - -```cpp - -// 串口接收缓冲区 -RingBuffer uart_rx_buffer; - -// 在串口中断中 -void UART_IRQHandler() { - if (UART->SR & UART_SR_RXNE) { - uint8_t data = UART->DR; - uart_rx_buffer.push(data); - } -} - -// 在主循环中处理 -void process_uart_data() { - uint8_t byte; - while (uart_rx_buffer.pop(byte)) { - // 处理接收到的数据 - protocol_parse(byte); - } -} - -// 传感器数据缓冲 -struct SensorData { - uint32_t timestamp; - float value; -}; - -RingBuffer sensor_buffer; - -// 采集数据 -void sample_sensor() { - SensorData data{ - .timestamp = get_system_tick(), - .value = read_adc() - }; - - if (!sensor_buffer.push(data)) { - // 缓冲区满,记录错误 - error_count++; - } -} - -// 批量发送 -void send_sensor_data() { - SensorData data; - while (sensor_buffer.pop(data)) { - send_to_host(data); - } -} -``` - ------- - -## 类模板特化 - -### 全特化 - -为特定模板参数提供完全不同的实现: - -```cpp -// 主模板 -template -class FixedVector { -private: - T data_[N]; - std::size_t size_ = 0; -public: - // 通用实现 -}; - -// 针对bool的全特化(使用位压缩) -template -class FixedVector { -private: - static constexpr std::size_t byte_count = (N + 7) / 8; - uint8_t data_[byte_count]; - std::size_t size_ = 0; - -public: - void push_back(bool value) { - if (size_ < N) { - if (value) { - data_[size_ / 8] |= (1 << (size_ % 8)); - } - ++size_; - } - } - - bool get(std::size_t index) const { - return data_[index / 8] & (1 << (index % 8)); - } - - std::size_t size() const { return size_; } -}; - -// 使用 -FixedVector int_vec; // 使用主模板 -FixedVector bool_vec; // 使用特化版本(节省内存) -``` - -### 偏特化(C++允许) - -只特化部分模板参数: - -```cpp -// 主模板 -template -class SmartPointer { - // ... -}; - -// 偏特化:针对void类型 -template -class SmartPointer { - // void特有实现 -}; - -// 主模板:两个类型参数 -template -class StaticMap { - // ... -}; - -// 偏特化:固定Value类型为int -template -class StaticMap { - // 针对int值的优化实现 -}; -``` - -**重要**:函数模板不支持偏特化,只有类模板支持。 - -### 使用SFINAE实现条件特化 - -```cpp -#include - -// 主模板 -template -class OptimizedBuffer { -private: - T data_[N]; -public: - // 通用实现 - void process() { - for (std::size_t i = 0; i < N; ++i) { - data_[i] = T{}; - } - } -}; - -// 针对算术类型的特化 -template -class OptimizedBuffer>> { -private: - T data_[N]; -public: - // 使用memset优化 - void process() { - std::memset(data_, 0, sizeof(data_)); - } -}; - -// 使用 -OptimizedBuffer int_buffer; // 使用memset版本 -OptimizedBuffer str_buffer; // 使用循环版本 -``` - -### C++17 using声明简化特化 - -```cpp -template -using FastBuffer = RingBuffer; - -template -using LargeBuffer = RingBuffer; - -// 使用 -FastBuffer uart_tx; -LargeBuffer sensor_data; -``` - ------- - -## 调试技巧:理解模板实例化错误信息 - -模板编译错误以"信息爆炸"著称。一个简单的错误可能产生数百行错误信息。让我们学习如何快速定位问题。 - -### 典型错误信息结构 - -```text -error: no matching function for call to 'foo' - foo(42); - ^~~ -note: candidate template ignored: deduced conflicting types for parameter 'T' ('int' vs 'double') - template - void foo(T a, T b); - ^ -``` - -关键信息: - -1. **错误位置**:`foo(42)`所在的行 -2. **失败原因**:`deduced conflicting types`(类型冲突) -3. **候选声明**:被考虑的模板声明 - -### 常见模板错误模式 - -#### 模式1:类型推导失败 - -```cpp -template -void process(T& a, T& b); - -int x; -double y; - -process(x, y); // 错误:T无法同时推导为int和double -``` - -**错误信息特征**: - -```text -note: template argument deduction/substitution failed: -note: couldn't deduce template parameter 'T' -``` - -**解决方法**: - -```cpp -// 方案1:显式指定类型 -process(x, y); - -// 方案2:两个模板参数 -template -void process(T& a, U& b); -``` - -#### 模式2:成员函数访问失败 - -```cpp -template -void process(T& value) { - value.some_method(); // T可能没有some_method -} -``` - -**错误信息特征**: - -```text -error: no member named 'some_method' in 'X' -``` - -**解决方法**:使用约束或if constexpr - -```cpp -template -auto process(T& value) -> decltype(value.some_method(), void()) { - value.some_method(); -} - -// 或C++17 -template -void process(T& value) { - if constexpr (requires { value.some_method(); }) { - value.some_method(); - } -} -``` - -#### 模式3:实例化深度过大 - -```cpp -template -struct Factorial { - static constexpr int value = N * Factorial::value; -}; - -Factorial<1000000> x; // 递归太深 -``` - -**错误信息特征**: - -```text -fatal error: template instantiation depth exceeds maximum of 900 -``` - -**解决方法**:使用constexpr函数 - -```cpp -constexpr int factorial(int n) { - return n <= 1 ? 1 : n * factorial(n - 1); -} -``` - -### 调试技巧总结 - -| 技巧 | 说明 | 示例 | -|------|------|------| -| 使用`static_assert` | 在模板开头添加约束 | `static_assert(std::is_integral_v)` | -| 查看错误位置 | 向上滚动找到首次错误位置 | 找到"instantiated from" | -| 使用`-fverbose-asm` | 查看生成的汇编 | 理解模板展开结果 | -| 使用`-ftime-report` | 分析编译时间 | 找出编译慢的模板 | -| 使用C++20 Concepts | 约束模板参数 | `template` | - -### 使用概念(C++20)简化错误信息 - -```cpp -// C++17之前:错误信息混乱 -template -void process(T t) { - static_assert(std::is_integral_v, "T must be integral"); - t += 1; -} - -// C++20:清晰的错误信息 -template -void process(T t) { - t += 1; -} - -// 如果调用 process(3.14),错误信息是: -// error: no matching function for call to 'process' -// note: candidate template ignored: constraints not satisfied -``` - -### 实用调试宏 - -```cpp -// 打印类型信息(用于调试) -template -struct print_type; - -// 使用时会导致编译错误,从而显示类型 -print_type x; - -// 错误信息会显示: -// error: implicit instantiation of undefined template 'print_type' -``` - -```cpp -// 编译期类型检查 -template -struct check_same { - static_assert(std::is_same_v, "Types must be the same"); - using type = T; -}; - -// 使用 -check_same{}; -``` - ------- - -## 嵌入式实战场景 - -### 场景1:寄存器映射模板 - -```cpp -template -class Register { -public: - // volatile确保读写不被优化 - inline RegisterType read() const { - return *reinterpret_cast(Address); - } - - inline void write(RegisterType value) const { - *reinterpret_cast(Address) = value; - } - - // 位操作 - void set_bits(RegisterType mask) const { - write(read() | mask); - } - - void clear_bits(RegisterType mask) const { - write(read() & ~mask); - } -}; - -// 使用 -using GPIOA_ODR = Register; -using GPIOA_MODER = Register; - -void led_on() { - GPIOA_ODR od; - od.set_bits(1 << 5); // 设置PA5 -} - -void led_off() { - GPIOA_ODR od; - od.clear_bits(1 << 5); // 清除PA5 -} -``` - -### 场景2:DMA描述符模板 - -```cpp -template -class DMADescriptor { -private: - struct Descriptor { - T* source; - T* destination; - std::size_t count; - uint32_t config; - }; - - Descriptor desc_; - -public: - DMADescriptor(T* src, T* dst, uint32_t cfg = 0) - : desc_{src, dst, Count, cfg} {} - - const Descriptor* get() const { return &desc_; } - - // 链式传输 - template - DMADescriptor* chain(DMADescriptor& next) { - desc_.config |= reinterpret_cast(next.get()); - return &next; - } -}; - -// 使用 -uint8_t rx_buffer[256]; -uint8_t tx_buffer[256]; -DMADescriptor rx_desc(nullptr, rx_buffer); -DMADescriptor tx_desc(tx_buffer, nullptr); - -// 配置DMA -DMA->CH0.SAR = reinterpret_cast(rx_desc.get()); -``` - -### 场景3:延迟模板(编译期计算) - -```cpp -template -class Delay { - static constexpr std::size_t cycles_per_us = Clock::frequency / 1000000; - static constexpr std::size_t total_cycles = DelayMicroseconds * cycles_per_us; - -public: - static inline void wait() { - // 编译期展开的循环 - for (volatile std::size_t i = 0; i < total_cycles; ++i) { - __NOP(); - } - } -}; - -// 使用 -struct SystemClock { - static constexpr std::size_t frequency = 72000000; // 72MHz -}; - -using Delay1ms = Delay; -using Delay10us = Delay; - -Delay1ms::wait(); // 编译期确定循环次数 -``` - -### 场景4:类型安全的外设ID - -```cpp -template -class PeripheralID { -private: - static constexpr std::size_t base_address = get_base_address(Instance); - static constexpr std::size_t irq_number = get_irq_number(Instance); - -public: - static constexpr std::size_t base() { return base_address; } - static constexpr std::size_t irq() { return irq_number; } - - // 启用时钟 - static void enable_clock() { - auto rcc = Register{}; - rcc.set_bits(1 << Instance); - } -}; - -// 使用 -using UART1 = PeripheralID; -using UART2 = PeripheralID; - -void init_uart() { - UART1::enable_clock(); - auto uart_cr1 = Register{}; - uart_cr1.write(0x0000); // 配置UART -} -``` - ------- - -## 常见陷阱与解决方案 - -### 陷阱1:模板定义和声明分离 - -```cpp -// header.h -template -class MyClass { -public: - void func(); // 声明 -}; - -// source.cpp -template -void MyClass::func() { // ❌ 错误:非内联成员函数 - // 实现 -} -``` - -**问题**:编译器在使用点需要看到完整的模板定义。 - -**解决方案**: - -```cpp -// 方案1:全部放在头文件 -template -class MyClass { -public: - void func() { // ✅ 内联定义 - // 实现 - } -}; - -// 方案2:显式实例化 -// header.h -template -class MyClass { -public: - void func(); -}; - -// source.cpp -#include "header.h" - -template -void MyClass::func() { - // 实现 -} - -// 显式实例化常用类型 -template class MyClass; -template class MyClass; -``` - -### 陷阱2:依赖类型的名称解析 - -```cpp -template -class MyClass { - typedef int value_type; // 或 using value_type = int; - - void func() { - value_type x = 0; // ❌ 错误:依赖类型需要typename - } -}; -``` - -**解决方案**: - -```cpp -template -class MyClass { - typedef typename T::value_type value_type; - - void func() { - typename T::value_type x = 0; // ✅ 使用typename - } -}; -``` - -### 陷阱3:this指针的依赖 - -```cpp -template -class Base { -public: - void func() {} -}; - -template -class Derived : public Base { -public: - void method() { - func(); // ❌ 可能找不到func - this->func(); // ✅ 显式使用this - } -}; -``` - -### 陷阱4:模板友元 - -```cpp -template -class MyClass { - // 让所有MyClass成为友元 - template - friend class MyClass; - -private: - T data_; -}; - -// 或者:让特定实例成为友元 -template -class Container { - friend void special_func<>(Container&); // 函数模板友元 -}; -``` - -### 陷阱5:虚函数与模板混用 - -```cpp -template -class Base { -public: - // ❌ 错误:虚函数不能是模板 - template - virtual void func(U arg) {} -}; -``` - -**解决方案**:使用类型擦除或重载 - -```cpp -// 方案1:类型擦除 -class Base { -public: - virtual ~Base() = default; - virtual void process_int(int) = 0; - virtual void process_double(double) = 0; -}; - -// 方案2:使用std::function -class DynamicProcessor { - std::function int_fn_; - std::function double_fn_; -public: - template - void register_handler() { - if constexpr (std::is_same_v) { - int_fn_ = [](int x) { /* 处理 */ }; - } - } -}; -``` - ------- - -## C++14/17/20 新特性 - -### C++14:变量模板 - -```cpp -// 变量模板 -template -constexpr T pi = T(3.1415926535897932385); - -// 使用 -float f = pi; -double d = pi; - -// 嵌入式应用:寄存器地址 -template -constexpr std::size_t register_address = base_address + N * sizeof(T); - -// 使用 -auto uart_dr = Register>{}; -``` - -### C++14:泛型lambda - -```cpp -template -void process_container(T& container) { - // 使用auto&&参数的lambda - auto for_each = [](auto&& fn) { - return [&](auto&&... args) { - fn(std::forward(args)...); - }; - }; -} -``` - -### C++17:类模板参数推导(CTAD) - -```cpp -// 不再需要指定模板参数 -std::pair p(42, "hello"); // 推导为pair -std::vector v{1, 2, 3}; // 推导为vector -std::array a{1, 2, 3, 4}; // 推导为array - -// 自定义类型也可以 -template -struct Buffer { - Buffer(T (&arr)[N]) : /* ... */ {} -}; - -Buffer buf = {1, 2, 3}; // 推导为Buffer -``` - -### C++17:if constexpr - -简化模板内的条件代码: - -```cpp -template -auto serialize(T value) { - if constexpr (std::is_integral_v) { - return std::to_string(value); - } else if constexpr (std::is_floating_point_v) { - return std::to_string(value); - } else if constexpr (std::is_same_v) { - return value; - } else { - return std::string("[unknown]"); - } -} -``` - -### C++17:inline变量 - -```cpp -template -class Counter { -public: - static inline std::size_t count = 0; // C++17:头文件中定义 -}; - -// 不需要在cpp文件中定义! -``` - -### C++17:折叠表达式 - -简化可变参数模板: - -```cpp -// 模板递归写法(C++14) -template -auto sum(Args... args) { - return (args + ...); // C++17折叠表达式 -} - -// 嵌入式应用:多通道初始化 -template -void init_pins(Pins... pins) { - (init_pin(pins), ...); // 对每个引脚调用init_pin -} - -// 使用 -init_pins(GPIOA_5, GPIOA_6, GPIOB_0); -``` - -### C++20:概念约束 - -```cpp -// 定义概念 -template -concept Integral = std::is_integral_v; - -template -concept SignedIntegral = Integral && std::is_signed_v; - -// 使用概念 -template -T abs(T value) { - return value < 0 ? -value : value; -} - -// 缩写函数模板 -void process(Integral auto value) { - // ... -} - -// 嵌入式应用:外设概念 -template -concept UART = requires(T t) { - { t.read() } -> std::convertible_to; - { t.write(uint8_t{}) } -> std::same_as; -}; - -template -void send_string(U& uart, const char* str) { - while (*str) uart.write(*str++); -} -``` - -### C++20:requires表达式 - -```cpp -template -concept Serializable = requires(T t) { - { t.serialize() } -> std::convertible_to>; - { t.deserialize(std::declval>()) }; -}; - -// 使用 -template -void send_object(T& obj) { - auto data = obj.serialize(); - // 发送数据 -} -``` - ------- - -## 小结 - -类模板是C++泛型编程的核心工具,在嵌入式开发中尤为重要: - -| 特性 | 嵌入式优势 | 应用场景 | -|------|-----------|----------| -| 编译期确定大小 | 零堆分配 | 固定容量容器 | -| 类型参数化 | 代码复用 | 通信协议、数据结构 | -| 非类型参数 | 常量表达式 | 寄存器映射、缓冲区 | -| 编译期多态 | 零开销抽象 | 算法特化、策略模式 | -| 内联优化 | 减少函数调用 | 热路径代码 | - -**实践建议**: - -1. **优先使用类模板而非宏**:类型安全且可调试 -2. **合理使用非类型模板参数**:编译期确定大小,避免动态分配 -3. **注意代码膨胀**:通过共享基类、显式实例化控制 -4. **善用C++17/20特性**:`if constexpr`、Concepts让模板代码更清晰 -5. **虚函数与模板分开**:运行时多态用虚函数,编译期多态用模板 - -**下一章**,我们将探讨**模板元编程**,学习类型萃取、SFINAE、可变参数模板等高级技巧,实现编译期的算法选择和类型计算。 diff --git a/documents/vol4-advanced/03-template-specialization.md b/documents/vol4-advanced/03-template-specialization.md deleted file mode 100644 index c7b05ce24..000000000 --- a/documents/vol4-advanced/03-template-specialization.md +++ /dev/null @@ -1,1981 +0,0 @@ ---- -chapter: 12 -cpp_standard: -- 11 -- 14 -- 17 -- 20 -description: 深入理解C++模板特化机制与匹配规则 -difficulty: intermediate -order: 3 -platform: host -prerequisites: -- 'Chapter 12: 模板入门概述' -- 'Chapter 12: 函数模板详解' -reading_time_minutes: 62 -tags: -- cpp-modern -- host -- intermediate -title: 模板特化与偏特化 ---- -# 嵌入式现代C++教程——模板特化与偏特化 - -在前面的章节中,我们学习了模板的基础语法和函数模板的推导规则。但有时候,通用的模板定义并不适合所有类型——你可能需要为某个特定类型提供完全不同的实现,或者为某类类型提供定制行为。 - -这就是**模板特化**发挥作用的地方。它是C++模板编程中最强大的工具之一,也是实现类型萃取(Type Traits)的基础。 - -本章我们将深入探讨: - -- 全特化与偏特化的本质区别 -- 类模板偏特化的匹配规则 -- 为什么函数模板应该用重载而非特化 -- 实战:实现一个完整的 traits 类 - ------- - -## 特化概述:为什么需要特化? - -### 通用模板的局限性 - -假设我们正在编写一个通用的比较工具: - -```cpp -template -bool equals(const T& a, const T& b) { - return a == b; -} -``` - -对于大多数类型,这工作得很好。但考虑这些场景: - -```cpp -// 场景1:C风格字符串 -equals("hello", "hello"); // 比较指针地址,而非字符串内容! - -// 场景2:浮点数 -equals(0.1 + 0.2, 0.3); // 浮点精度问题,可能返回false - -// 场景3:指针类型 -equals(&p1, &p2); // 比较指针值,可能不是你想要的 -``` - -这就是特化的用武之地——**为特定类型提供专门的实现**。 - -### 特化的分类 - -C++模板特化分为两大类: - -| 特化类型 | 类模板 | 函数模板 | -|----------|--------|----------| -| 全特化(Explicit Specialization) | 支持 | 支持(但不推荐) | -| 偏特化(Partial Specialization) | 支持 | 不支持 | - -我们接下来详细探讨这两种特化。 - ------- - -## 全特化:完全定制 - -### 什么是全特化? - -全特化是为**特定模板参数**提供完全独立的实现。特化后,原模板的所有代码都可以被替换。 - -### 类模板的全特化 - -```cpp -// 主模板(通用版本) -template -class TypeInfo { -public: - static const char* name() { return "unknown"; } - static constexpr size_t size = sizeof(T); -}; - -// 为int全特化 -template<> -class TypeInfo { -public: - static const char* name() { return "signed integer"; } - static constexpr size_t size = sizeof(int); - - // 特化版本可以添加额外的成员 - static constexpr bool is_signed = true; - static constexpr int min_value = INT_MIN; - static constexpr int max_value = INT_MAX; -}; - -// 为double全特化 -template<> -class TypeInfo { -public: - static const char* name() { return "double precision float"; } - static constexpr size_t size = sizeof(double); - static constexpr bool is_signed = true; -}; - -// 使用 -std::cout << TypeInfo::name(); // "unknown"(使用主模板) -std::cout << TypeInfo::name(); // "signed integer"(使用特化) -std::cout << TypeInfo::name(); // "double precision float" -``` - -**语法要点**: - -- `template<>` 表示这是一个特化 -- 后面紧跟类名和具体类型:`class TypeInfo` -- 特化版本可以完全重新定义类的内容 - -### 函数模板的全特化 - -```cpp -// 主模板 -template -bool is_negative(T value) { - return value < 0; -} - -// 为unsigned int全特化 -template<> -bool is_negative(unsigned int value) { - (void)value; // 避免unused参数警告 - return false; // 无符号数永远不为负 -} - -// 使用 -is_negative(-5); // 调用主模板,T=int -is_negative(5u); // 调用特化版本 -is_negative(3.14); // 调用主模板,T=double -``` - -### 嵌入式应用场景:寄存器访问特化 - -```cpp -// 通用的寄存器访问模板 -template -class Register { -public: - static void write(T value) { - *reinterpret_cast(Address) = value; - } - - static T read() { - return *reinterpret_cast(Address); - } -}; - -// 为布尔寄存器(1位)全特化 -template -class Register { -public: - static void write(bool value) { - if (value) { - *reinterpret_cast(Address) = 1; - } else { - *reinterpret_cast(Address) = 0; - } - } - - static bool read() { - return *reinterpret_cast(Address) != 0; - } - - // 添加特有操作 - static void set() { - *reinterpret_cast(Address) = 1; - } - - static void clear() { - *reinterpret_cast(Address) = 0; - } - - static void toggle() { - auto reg = reinterpret_cast(Address); - *reg = !*reg; - } -}; - -// 使用 -Register gpio_mode; // 通用版本 -Register gpio_output; // 特化版本,有set/clear/toggle - -gpio_mode.write(0x44444444); -gpio_output.set(); -``` - ------- - -## 偏特化:部分定制 - -### 什么是偏特化? - -偏特化(部分特化)只对**部分模板参数**进行特化,或者对参数的**某种属性**进行特化。 - -**关键点**: - -- 只有**类模板**支持偏特化 -- 函数模板**不支持**偏特化 - -### 基于模板参数数量的偏特化 - -```cpp -// 主模板:两个类型参数 -template -class Pair { -public: - T first; - U second; - Pair() : first{}, second{} {} - Pair(const T& t, const U& u) : first(t), second(u) {} - - void print() const { - std::cout << "Pair: " << first << ", " << second << '\n'; - } -}; - -// 偏特化:两个类型相同时 -template -class Pair { -public: - T first; - T second; - Pair() : first{}, second{} {} - Pair(const T& t, const T& u) : first(t), second(u) {} - - // 可以添加针对相同类型的特殊操作 - T sum() const { return first + second; } - bool are_equal() const { return first == second; } - - void print() const { - std::cout << "Pair: " << first << ", " << second << '\n'; - } -}; - -// 偏特化:第二个参数是指针 -template -class Pair { -public: - T first; - U* second; - Pair() : first{}, second{nullptr} {} - Pair(const T& t, U* u) : first(t), second(u) {} - - // 针对指针的特殊处理 - void print() const { - std::cout << "Pair: " << first << ", " - << (second ? *second : U{}) << '\n'; - } -}; - -// 使用 -Pair p1; // 使用主模板 -Pair p2; // 使用偏特化 -Pair p3; // 使用偏特化 -``` - -### 基于类型属性的偏特化 - -这是偏特化最强大的用法——针对类型的**性质**而非类型本身: - -```cpp -// 主模板 -template -class TypeProperties { -public: - static constexpr bool is_pointer = false; - static constexpr bool is_const = false; - static constexpr bool is_reference = false; - using value_type = T; - using base_type = T; -}; - -// 指针类型的偏特化 -template -class TypeProperties { -public: - static constexpr bool is_pointer = true; - static constexpr bool is_const = false; - static constexpr bool is_reference = false; - using value_type = T; - using base_type = T; // 去掉指针后的类型 -}; - -// const指针的偏特化 -template -class TypeProperties { -public: - static constexpr bool is_pointer = true; - static constexpr bool is_const = true; - static constexpr bool is_reference = false; - using value_type = T; - using base_type = T; -}; - -// 引用类型的偏特化 -template -class TypeProperties { -public: - static constexpr bool is_pointer = false; - static constexpr bool is_const = false; - static constexpr bool is_reference = true; - using value_type = T; - using base_type = T; -}; - -// 使用 -TypeProperties::is_pointer; // false -TypeProperties::is_pointer; // true -TypeProperties::is_const; // true -TypeProperties::is_reference; // true -TypeProperties::base_type; // int -``` - -### 偏特化的层次结构 - -偏特化可以有多个层次,编译器会选择**最特化**的版本: - -```cpp -// 层次0:主模板 -template -class Container { -public: - static constexpr int level = 0; -}; - -// 层次1:指针偏特化 -template -class Container { -public: - static constexpr int level = 1; -}; - -// 层次2:const指针偏特化 -template -class Container { -public: - static constexpr int level = 2; -}; - -// 层次3:指向const的const指针 -template -class Container { -public: - static constexpr int level = 3; -}; - -// 使用 -Container::level; // 0(主模板) -Container::level; // 1(指针) -Container::level; // 2(const指针) -Container::level; // 3(const T* const) -``` - -### 多模板参数的偏特化匹配 - -当有多个偏特化版本时,编译器按照"最特化"原则选择: - -```cpp -// 主模板 -template -class Test { -public: - static constexpr const char* desc = "primary"; -}; - -// 偏特化1:固定第三个参数 -template -class Test { -public: - static constexpr const char* desc = "N=10"; -}; - -// 偏特化2:前两个参数相同 -template -class Test { -public: - static constexpr const char* desc = "T=U"; -}; - -// 偏特化3:前两个参数相同且N=10 -template -class Test { -public: - static constexpr const char* desc = "T=U, N=10"; -}; - -// 使用 -Test::desc; // "primary"(主模板) -Test::desc; // "N=10"(偏特化1) -Test::desc; // "T=U"(偏特化2) -Test::desc; // "T=U, N=10"(偏特化3,最特化) -``` - -### 编译器如何选择"最特化"版本? - -偏特化选择遵循**更特化**(more specialized)规则: - -| 规则 | 说明 | -|------|------| -| 部分参数固定 | 固定部分参数的比全参数的更特化 | -| 约束更多 | 约束更多的版本更特化 | -| 更具体的匹配 | 能匹配更多类型的版本更通用 | - -```cpp -// 示例:理解"更特化" -template -struct A { static constexpr int value = 0; }; // 通用 - -template -struct A { static constexpr int value = 1; }; // 更特化(指针) - -template -struct A { static constexpr int value = 2; }; // 最特化(const指针) - -A::value; // 0 -A::value; // 1 -A::value; // 2 -``` - ------- - -## 匹配规则详解 - -### 完整的匹配顺序 - -当编译器遇到模板使用时,按以下顺序匹配: - -```text -1. 尝试完全匹配的全特化 -2. 尝试匹配所有偏特化,选择最特化的 -3. 如果没有特化匹配,使用主模板 -4. 如果都没有,编译错误 -``` - -### 匹配示例 - -```cpp -// 主模板 -template -class Selector { -public: - static constexpr int choice = 0; -}; - -// 偏特化:T是指针 -template -class Selector { -public: - static constexpr int choice = 1; -}; - -// 偏特化:U是指针 -template -class Selector { -public: - static constexpr int choice = 2; -}; - -// 偏特化:两者都是指针 -template -class Selector { -public: - static constexpr int choice = 3; -}; - -// 测试 -static_assert(Selector::choice == 0); // 主模板 -static_assert(Selector::choice == 1); // T*偏特化 -static_assert(Selector::choice == 2); // U*偏特化 -static_assert(Selector::choice == 3); // 都是指针 -``` - -### 引用和cv限定符的匹配 - -```cpp -// 主模板 -template -class Matcher { -public: - static constexpr int value = 0; -}; - -// const偏特化 -template -class Matcher { -public: - static constexpr int value = 1; -}; - -// 引用偏特化 -template -class Matcher { -public: - static constexpr int value = 2; -}; - -// rvalue引用偏特化 -template -class Matcher { -public: - static constexpr int value = 3; -}; - -// const引用偏特化 -template -class Matcher { -public: - static constexpr int value = 4; -}; - -// 测试 -static_assert(Matcher::value == 0); // 主模板 -static_assert(Matcher::value == 1); // const偏特化 -static_assert(Matcher::value == 2); // 引用偏特化 -static_assert(Matcher::value == 3); // rvalue引用 -static_assert(Matcher::value == 4); // const引用 -``` - -### 注意事项:退化与匹配 - -类型在匹配时会考虑退化(decay): - -```cpp -// 主模板 -template -class DecayTest { -public: - static constexpr int value = 0; - static constexpr const char* name = "primary"; -}; - -// 数组偏特化 -template -class DecayTest { -public: - static constexpr int value = 1; - static constexpr const char* name = "array"; -}; - -// 函数偏特化 -template -class DecayTest { -public: - static constexpr int value = 2; - static constexpr const char* name = "function"; -}; - -// 指针偏特化 -template -class DecayTest { -public: - static constexpr int value = 3; - static constexpr const char* name = "pointer"; -}; - -// 测试 -DecayTest::name; // "array" -DecayTest::name; // "function" -DecayTest::name; // "pointer" -``` - ------- - -## 函数模板特化的真相 - -### 为什么函数模板"不支持"偏特化? - -准确地说:**C++允许函数模板的全特化,但不允许偏特化**。当你想要"部分特化"函数模板时,应该使用**函数重载**。 - -### 函数模板全特化的语法 - -```cpp -// 主模板 -template -void process(T value) { - std::cout << "Generic: " << value << '\n'; -} - -// 全特化:为int -template<> -void process(int value) { - std::cout << "Int specialized: " << value << '\n'; -} - -// 全特化:可以省略,编译器会推导 -template<> -void process(double value) { - std::cout << "Double specialized: " << value << '\n'; -} - -// 使用 -process(42); // 输出 "Int specialized: 42" -process(3.14); // 输出 "Double specialized: 3.14" -process('a'); // 输出 "Generic: a" -``` - -### 为什么不应该特化函数模板? - -这是个很常见的陷阱。函数模板特化有几个严重问题: - -#### 问题1:特化不参与重载决议 - -```cpp -// 主模板 -template -void func(T t) { - std::cout << "Template\n"; -} - -// 特化版本 -template<> -void func(int t) { - std::cout << "Specialization\n"; -} - -// 普通重载 -void func(int t) { - std::cout << "Overload\n"; -} - -// 调用 -func(42); // 输出 "Overload" —— 普通函数优先! -``` - -**特化版本的优先级低于普通函数重载**,这可能导致意想不到的行为。 - -#### 问题2:无法偏特化 - -```cpp -// ❌ 这不是合法的C++! -template -void process(T* ptr) { // 这看起来像偏特化,实际上是新的主模板 - std::cout << "Pointer\n"; -} - -// 这样做会创建一个新的主模板,导致二义性错误 -template -void process(T t) { - std::cout << "Generic\n"; -} - -process(42); // 错误!两个主模板都匹配 -``` - -### 解决方案:使用函数重载 - -**最佳实践**:用函数重载替代函数模板特化。 - -```cpp -// ✅ 正确方式:使用重载 - -// 主模板 -template -void func(T t) { - std::cout << "Generic: " << t << '\n'; -} - -// 重载:指针版本 -template -void func(T* ptr) { - std::cout << "Pointer: " << ptr << '\n'; -} - -// 重载:const char*版本 -void func(const char* str) { - std::cout << "C-string: " << str << '\n'; -} - -// 使用 -func(42); // "Generic: 42" -int x = 10; -func(&x); // "Pointer: <地址>" -func("hello"); // "C-string: hello" -``` - -### 特化与重载的决策表 - -| 场景 | 类模板 | 函数模板 | -|------|--------|----------| -| 需要完全定制 | 使用全特化 | 使用重载 | -| 需要部分定制 | 使用偏特化 | 使用重载 | -| 需要参与重载决议 | 不适用 | 使用重载 | -| 需要修改类成员 | 使用特化 | 不适用 | - -### 何时使用函数模板特化? - -函数模板特化应该只在以下有限场景使用: - -1. **修改类模板的成员函数**: - - ```cpp - template - class MyClass { - void func(); // 成员函数模板 - }; - - // 特化整个类的成员函数 - template<> - void MyClass::func() { - // int版本的实现 - } - ``` - -2. **与类模板特化配合**: - - ```cpp - template - class MyClass { - template - void helper(U u); - }; - - // 只能通过特化MyClass来特化helper - ``` - -除此之外,**优先使用函数重载**。 - ------- - -## 实战:实现完整的 Traits 类 - -Type Traits 是C++模板元编程的核心工具。让我们从头实现一个完整的 traits 类,包含指针类型的特化。 - -### 需求分析 - -我们的 traits 类需要提供: - -1. **类型信息**:基础类型、值类型、去掉引用/const的类型 -2. **类型属性**:是否为指针、引用、const、算术类型 -3. **类型转换**:添加/移除指针、引用、const -4. **类型关系**:两个类型是否相同、一个是否可转换为另一个 - -### 基础框架 - -```cpp -// 基础traits定义 -template -class TypeTraits { -public: - // 类型信息 - using BaseType = T; // 去掉所有修饰符后的类型 - using ValueType = T; // 容器存储的值类型 - using ReferenceType = T&; // 对应的引用类型 - using PointerType = T*; // 对应的指针类型 - - // 类型属性 - static constexpr bool is_pointer = false; - static constexpr bool is_reference = false; - static constexpr bool is_const = false; - static constexpr bool is_volatile = false; - static constexpr bool is_void = false; - static constexpr bool is_integral = false; - static constexpr bool is_floating_point = false; - static constexpr bool is_arithmetic = false; - static constexpr bool is_signed = false; - static constexpr bool is_unsigned = false; - - // 便捷常量 - static constexpr bool is_primitive = is_arithmetic || is_void; -}; -``` - -### 指针类型特化 - -```cpp -// 指针类型的偏特化 -template -class TypeTraits { -public: - using BaseType = typename TypeTraits::BaseType; // 递归获取 - using ValueType = T; // 指针指向的类型 - using ReferenceType = T&; - using PointerType = T*; - - static constexpr bool is_pointer = true; - static constexpr bool is_reference = false; - static constexpr bool is_const = TypeTraits::is_const; - static constexpr bool is_volatile = TypeTraits::is_volatile; - static constexpr bool is_void = TypeTraits::is_void; - static constexpr bool is_integral = TypeTraits::is_integral; - static constexpr bool is_floating_point = TypeTraits::is_floating_point; - static constexpr bool is_arithmetic = TypeTraits::is_arithmetic; - static constexpr bool is_signed = TypeTraits::is_signed; - static constexpr bool is_unsigned = TypeTraits::is_unsigned; - - static constexpr bool is_primitive = is_arithmetic || is_void; -}; - -// const指针的偏特化 -template -class TypeTraits { -public: - using BaseType = typename TypeTraits::BaseType; - using ValueType = const T; - using ReferenceType = const T&; - using PointerType = const T*; - - static constexpr bool is_pointer = true; - static constexpr bool is_reference = false; - static constexpr bool is_const = true; // 这是const指针 - static constexpr bool is_volatile = TypeTraits::is_volatile; - static constexpr bool is_void = TypeTraits::is_void; - static constexpr bool is_integral = TypeTraits::is_integral; - static constexpr bool is_floating_point = TypeTraits::is_floating_point; - static constexpr bool is_arithmetic = TypeTraits::is_arithmetic; - static constexpr bool is_signed = TypeTraits::is_signed; - static constexpr bool is_unsigned = TypeTraits::is_unsigned; - - static constexpr bool is_primitive = is_arithmetic || is_void; -}; -``` - -### 引用类型特化 - -```cpp -// 左值引用的偏特化 -template -class TypeTraits { -public: - using BaseType = typename TypeTraits::BaseType; - using ValueType = T; - using ReferenceType = T&; - using PointerType = T*; - - static constexpr bool is_pointer = false; - static constexpr bool is_reference = true; - static constexpr bool is_const = TypeTraits::is_const; - static constexpr bool is_volatile = TypeTraits::is_volatile; - static constexpr bool is_void = TypeTraits::is_void; - static constexpr bool is_integral = TypeTraits::is_integral; - static constexpr bool is_floating_point = TypeTraits::is_floating_point; - static constexpr bool is_arithmetic = TypeTraits::is_arithmetic; - static constexpr bool is_signed = TypeTraits::is_signed; - static constexpr bool is_unsigned = TypeTraits::is_unsigned; - - static constexpr bool is_primitive = is_arithmetic || is_void; -}; - -// 右值引用的偏特化 (C++11) -template -class TypeTraits { -public: - using BaseType = typename TypeTraits::BaseType; - using ValueType = T; - using ReferenceType = T&&; - using PointerType = T*; - - static constexpr bool is_pointer = false; - static constexpr bool is_reference = true; // rvalue也是引用 - static constexpr bool is_const = TypeTraits::is_const; - static constexpr bool is_volatile = TypeTraits::is_volatile; - static constexpr bool is_void = TypeTraits::is_void; - static constexpr bool is_integral = TypeTraits::is_integral; - static constexpr bool is_floating_point = TypeTraits::is_floating_point; - static constexpr bool is_arithmetic = TypeTraits::is_arithmetic; - static constexpr bool is_signed = TypeTraits::is_signed; - static constexpr bool is_unsigned = TypeTraits::is_unsigned; - - static constexpr bool is_primitive = is_arithmetic || is_void; -}; -``` - -### 基础类型的全特化 - -```cpp -// void的特化 -template<> -class TypeTraits { -public: - using BaseType = void; - using ValueType = void; - using ReferenceType = void; - using PointerType = void*; - - static constexpr bool is_pointer = false; - static constexpr bool is_reference = false; - static constexpr bool is_const = false; - static constexpr bool is_volatile = false; - static constexpr bool is_void = true; - static constexpr bool is_integral = false; - static constexpr bool is_floating_point = false; - static constexpr bool is_arithmetic = false; - static constexpr bool is_signed = false; - static constexpr bool is_unsigned = false; - - static constexpr bool is_primitive = true; -}; - -// 整数类型的宏辅助 -#define INTEGRAL_TRAITS(Type, IsSigned) \ -template<> \ -class TypeTraits { \ -public: \ - using BaseType = Type; \ - using ValueType = Type; \ - using ReferenceType = Type&; \ - using PointerType = Type*; \ -\ - static constexpr bool is_pointer = false; \ - static constexpr bool is_reference = false; \ - static constexpr bool is_const = false; \ - static constexpr bool is_volatile = false; \ - static constexpr bool is_void = false; \ - static constexpr bool is_integral = true; \ - static constexpr bool is_floating_point = false; \ - static constexpr bool is_arithmetic = true; \ - static constexpr bool is_signed = IsSigned; \ - static constexpr bool is_unsigned = !IsSigned; \ -\ - static constexpr bool is_primitive = true; \ -} - -// 为所有整数类型定义特化 -INTEGRAL_TRAITS(char, true); -INTEGRAL_TRAITS(signed char, true); -INTEGRAL_TRAITS(unsigned char, false); -INTEGRAL_TRAITS(short, true); -INTEGRAL_TRAITS(unsigned short, false); -INTEGRAL_TRAITS(int, true); -INTEGRAL_TRAITS(unsigned int, false); -INTEGRAL_TRAITS(long, true); -INTEGRAL_TRAITS(unsigned long, false); -INTEGRAL_TRAITS(long long, true); -INTEGRAL_TRAITS(unsigned long long, false); - -// 浮点类型的宏辅助 -#define FLOATING_TRAITS(Type) \ -template<> \ -class TypeTraits { \ -public: \ - using BaseType = Type; \ - using ValueType = Type; \ - using ReferenceType = Type&; \ - using PointerType = Type*; \ -\ - static constexpr bool is_pointer = false; \ - static constexpr bool is_reference = false; \ - static constexpr bool is_const = false; \ - static constexpr bool is_volatile = false; \ - static constexpr bool is_void = false; \ - static constexpr bool is_integral = false; \ - static constexpr bool is_floating_point = true; \ - static constexpr bool is_arithmetic = true; \ - static constexpr bool is_signed = true; \ - static constexpr bool is_unsigned = false; \ -\ - static constexpr bool is_primitive = true; \ -} - -FLOATING_TRAITS(float); -FLOATING_TRAITS(double); -FLOATING_TRAITS(long double); -``` - -### 辅助别名模板 - -```cpp -// 便捷的类型别名 -template -using BaseType_t = typename TypeTraits::BaseType; - -template -using ValueType_t = typename TypeTraits::ValueType; - -template -using AddPointer_t = typename TypeTraits::PointerType; - -template -using AddReference_t = typename TypeTraits::ReferenceType; - -// 检查类型属性的常量 -template -inline constexpr bool is_pointer_v = TypeTraits::is_pointer; - -template -inline constexpr bool is_reference_v = TypeTraits::is_reference; - -template -inline constexpr bool is_arithmetic_v = TypeTraits::is_arithmetic; -``` - -### 使用示例 - -```cpp -// 测试代码 -void test_type_traits() { - // 指针类型 - static_assert(is_pointer_v == true); - static_assert(is_pointer_v == false); - static_assert(is_pointer_v == true); - - // 引用类型 - static_assert(is_reference_v == true); - static_assert(is_reference_v == false); - static_assert(is_reference_v == true); - - // 算术类型 - static_assert(is_arithmetic_v == true); - static_assert(is_arithmetic_v == true); - static_assert(is_arithmetic_v == false); - - // 嵌套类型 - static_assert(std::is_same_v, int>); - static_assert(std::is_same_v, int>); - static_assert(std::is_same_v, int>); -} -``` - -### 嵌入式应用:智能指针 traits - -```cpp -// 用于嵌入式智能指针的traits -template -class SmartPtrTraits { -public: - using pointer_type = T*; - using element_type = T; - using deleter_type = void(*)(T*); - - static void default_delete(T* ptr) { - delete ptr; - } - - static constexpr bool is_array = false; - static constexpr bool is_shared = false; -}; - -// 数组类型的偏特化 -template -class SmartPtrTraits { -public: - using pointer_type = T*; - using element_type = T; - using deleter_type = void(*)(T*); - - static void default_delete(T* ptr) { - delete[] ptr; - } - - static constexpr bool is_array = true; - static constexpr bool is_shared = false; -}; - -// 使用traits的智能指针基类 -template> -class SmartPtrBase { -protected: - using Ptr = typename Traits::pointer_type; - Ptr ptr_; - - void cleanup() { - if (ptr_) { - Traits::default_delete(ptr_); - } - } - -public: - constexpr SmartPtrBase(std::nullptr_t = nullptr) noexcept : ptr_(nullptr) {} - - explicit SmartPtrBase(Ptr p) noexcept : ptr_(p) {} - - ~SmartPtrBase() { - cleanup(); - } - - // 禁用拷贝 - SmartPtrBase(const SmartPtrBase&) = delete; - SmartPtrBase& operator=(const SmartPtrBase&) = delete; - - // 支持移动 - SmartPtrBase(SmartPtrBase&& other) noexcept : ptr_(other.ptr_) { - other.ptr_ = nullptr; - } - - SmartPtrBase& operator=(SmartPtrBase&& other) noexcept { - if (this != &other) { - cleanup(); - ptr_ = other.ptr_; - other.ptr_ = nullptr; - } - return *this; - } - - // 访问器 - Ptr get() const noexcept { return ptr_; } - explicit operator bool() const noexcept { return ptr_ != nullptr; } - - // 特化操作 - void reset(Ptr p = nullptr) noexcept { - cleanup(); - ptr_ = p; - } - - Ptr release() noexcept { - Ptr tmp = ptr_; - ptr_ = nullptr; - return tmp; - } -}; - -// 具体的智能指针实现 -template -class UniquePtr : public SmartPtrBase> { - using Base = SmartPtrBase>; - -public: - using Base::Base; - - // 解引用操作符 - T& operator*() const { return *this->get(); } - T* operator->() const { return this->get(); } -}; - -// 数组特化版本 -template -class UniquePtr : public SmartPtrBase> { - using Base = SmartPtrBase>; - -public: - using Base::Base; - - // 数组下标操作 - T& operator[](size_t index) const { return this->get()[index]; } -}; - -// 使用 -UniquePtr ptr1(new int(42)); -UniquePtr arr(new int[10]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); -``` - -::: details 完整 TypeTraits 实现(可展开) - -```cpp -#ifndef TYPE_TRAITS_HPP -#define TYPE_TRAITS_HPP - -#include -#include - -template -class TypeTraits { -public: - using BaseType = T; - using ValueType = T; - using ReferenceType = T&; - using PointerType = T*; - - static constexpr bool is_pointer = false; - static constexpr bool is_reference = false; - static constexpr bool is_const = false; - static constexpr bool is_volatile = false; - static constexpr bool is_void = false; - static constexpr bool is_integral = false; - static constexpr bool is_floating_point = false; - static constexpr bool is_arithmetic = false; - static constexpr bool is_signed = false; - static constexpr bool is_unsigned = false; - static constexpr bool is_primitive = is_arithmetic || is_void; -}; - -// 指针偏特化 -template -class TypeTraits { -public: - using BaseType = typename TypeTraits::BaseType; - using ValueType = T; - using ReferenceType = T&; - using PointerType = T*; - - static constexpr bool is_pointer = true; - static constexpr bool is_const = TypeTraits::is_const; - static constexpr bool is_volatile = TypeTraits::is_volatile; - static constexpr bool is_void = TypeTraits::is_void; - static constexpr bool is_integral = TypeTraits::is_integral; - static constexpr bool is_floating_point = TypeTraits::is_floating_point; - static constexpr bool is_arithmetic = TypeTraits::is_arithmetic; - static constexpr bool is_signed = TypeTraits::is_signed; - static constexpr bool is_unsigned = TypeTraits::is_unsigned; - static constexpr bool is_primitive = is_arithmetic || is_void; -}; - -// const指针偏特化 -template -class TypeTraits { -public: - using BaseType = typename TypeTraits::BaseType; - using ValueType = const T; - using ReferenceType = const T&; - using PointerType = const T*; - - static constexpr bool is_pointer = true; - static constexpr bool is_const = true; - static constexpr bool is_volatile = TypeTraits::is_volatile; - static constexpr bool is_void = TypeTraits::is_void; - static constexpr bool is_integral = TypeTraits::is_integral; - static constexpr bool is_floating_point = TypeTraits::is_floating_point; - static constexpr bool is_arithmetic = TypeTraits::is_arithmetic; - static constexpr bool is_signed = TypeTraits::is_signed; - static constexpr bool is_unsigned = TypeTraits::is_unsigned; - static constexpr bool is_primitive = is_arithmetic || is_void; -}; - -// 引用偏特化 -template -class TypeTraits { -public: - using BaseType = typename TypeTraits::BaseType; - using ValueType = T; - using ReferenceType = T&; - using PointerType = T*; - - static constexpr bool is_reference = true; - static constexpr bool is_const = TypeTraits::is_const; - static constexpr bool is_volatile = TypeTraits::is_volatile; - static constexpr bool is_void = TypeTraits::is_void; - static constexpr bool is_integral = TypeTraits::is_integral; - static constexpr bool is_floating_point = TypeTraits::is_floating_point; - static constexpr bool is_arithmetic = TypeTraits::is_arithmetic; - static constexpr bool is_signed = TypeTraits::is_signed; - static constexpr bool is_unsigned = TypeTraits::is_unsigned; - static constexpr bool is_primitive = is_arithmetic || is_void; -}; - -// rvalue引用偏特化 -template -class TypeTraits { -public: - using BaseType = typename TypeTraits::BaseType; - using ValueType = T; - using ReferenceType = T&&; - using PointerType = T*; - - static constexpr bool is_reference = true; - static constexpr bool is_const = TypeTraits::is_const; - static constexpr bool is_volatile = TypeTraits::is_volatile; - static constexpr bool is_void = TypeTraits::is_void; - static constexpr bool is_integral = TypeTraits::is_integral; - static constexpr bool is_floating_point = TypeTraits::is_floating_point; - static constexpr bool is_arithmetic = TypeTraits::is_arithmetic; - static constexpr bool is_signed = TypeTraits::is_signed; - static constexpr bool is_unsigned = TypeTraits::is_unsigned; - static constexpr bool is_primitive = is_arithmetic || is_void; -}; - -// void特化 -template<> -class TypeTraits { -public: - using BaseType = void; - using ValueType = void; - using ReferenceType = void; - using PointerType = void*; - - static constexpr bool is_pointer = false; - static constexpr bool is_reference = false; - static constexpr bool is_const = false; - static constexpr bool is_volatile = false; - static constexpr bool is_void = true; - static constexpr bool is_integral = false; - static constexpr bool is_floating_point = false; - static constexpr bool is_arithmetic = false; - static constexpr bool is_signed = false; - static constexpr bool is_unsigned = false; - static constexpr bool is_primitive = true; -}; - -// 整数类型宏 -#define INTEGRAL_TRAITS(Type, IsSigned) \ -template<> \ -class TypeTraits { \ -public: \ - using BaseType = Type; \ - using ValueType = Type; \ - using ReferenceType = Type&; \ - using PointerType = Type*; \ -\ - static constexpr bool is_pointer = false; \ - static constexpr bool is_reference = false; \ - static constexpr bool is_const = false; \ - static constexpr bool is_volatile = false; \ - static constexpr bool is_void = false; \ - static constexpr bool is_integral = true; \ - static constexpr bool is_floating_point = false; \ - static constexpr bool is_arithmetic = true; \ - static constexpr bool is_signed = IsSigned; \ - static constexpr bool is_unsigned = !IsSigned; \ - static constexpr bool is_primitive = true; \ -} - -INTEGRAL_TRAITS(char, true); -INTEGRAL_TRAITS(signed char, true); -INTEGRAL_TRAITS(unsigned char, false); -INTEGRAL_TRAITS(short, true); -INTEGRAL_TRAITS(unsigned short, false); -INTEGRAL_TRAITS(int, true); -INTEGRAL_TRAITS(unsigned int, false); -INTEGRAL_TRAITS(long, true); -INTEGRAL_TRAITS(unsigned long, false); -INTEGRAL_TRAITS(long long, true); -INTEGRAL_TRAITS(unsigned long long, false); -INTEGRAL_TRAITS(int8_t, true); -INTEGRAL_TRAITS(int16_t, true); -INTEGRAL_TRAITS(int32_t, true); -INTEGRAL_TRAITS(int64_t, true); -INTEGRAL_TRAITS(uint8_t, false); -INTEGRAL_TRAITS(uint16_t, false); -INTEGRAL_TRAITS(uint32_t, false); -INTEGRAL_TRAITS(uint64_t, false); - -// 浮点类型宏 -#define FLOATING_TRAITS(Type) \ -template<> \ -class TypeTraits { \ -public: \ - using BaseType = Type; \ - using ValueType = Type; \ - using ReferenceType = Type&; \ - using PointerType = Type*; \ -\ - static constexpr bool is_pointer = false; \ - static constexpr bool is_reference = false; \ - static constexpr bool is_const = false; \ - static constexpr bool is_volatile = false; \ - static constexpr bool is_void = false; \ - static constexpr bool is_integral = false; \ - static constexpr bool is_floating_point = true; \ - static constexpr bool is_arithmetic = true; \ - static constexpr bool is_signed = true; \ - static constexpr bool is_unsigned = false; \ - static constexpr bool is_primitive = true; \ -} - -FLOATING_TRAITS(float); -FLOATING_TRAITS(double); -FLOATING_TRAITS(long double); - -// 便捷别名和变量模板 -template -using BaseType_t = typename TypeTraits::BaseType; - -template -using ValueType_t = typename TypeTraits::ValueType; - -template -using AddPointer_t = typename TypeTraits::PointerType; - -template -using AddReference_t = typename TypeTraits::ReferenceType; - -template -inline constexpr bool is_pointer_v = TypeTraits::is_pointer; - -template -inline constexpr bool is_reference_v = TypeTraits::is_reference; - -template -inline constexpr bool is_arithmetic_v = TypeTraits::is_arithmetic; - -template -inline constexpr bool is_integral_v = TypeTraits::is_integral; - -template -inline constexpr bool is_floating_point_v = TypeTraits::is_floating_point; - -#endif // TYPE_TRAITS_HPP - - ``` - -::: - ------- - -## 标准库溯源:std::iterator_traits - -### 设计背景 - -在C++标准库中,`std::iterator_traits` 是特化的经典应用案例。它允许算法以统一方式处理不同类型的迭代器。 - -### iterator_traits 的结构 - -``` - -// C++标准库中的简化版本 -namespace std { - -template -class iterator_traits { -public: - using iterator_category = typename Iterator::iterator_category; - using value_type = typename Iterator::value_type; - using difference_type = typename Iterator::difference_type; - using pointer = typename Iterator::pointer; - using reference = typename Iterator::reference; -}; - -// 指针类型的偏特化! -template -class iterator_traits { -public: - using iterator_category = random_access_iterator_tag; - using value_type = T; - using difference_type = ptrdiff_t; - using pointer = T*; - using reference = T&; -}; - -// const指针的偏特化 -template -class iterator_traits { -public: - using iterator_category = random_access_iterator_tag; - using value_type = T; // 注意:是T而非const T - using difference_type = ptrdiff_t; - using pointer = const T*; - using reference = const T&; -}; - -} // namespace std - -```cpp - -### 为什么需要指针特化? - -考虑一个自定义迭代器: - -```cpp - -template -class MyIterator { -public: - using iterator_category = std::forward_iterator_tag; - using value_type = T; - using difference_type = std::ptrdiff_t; - using pointer = T*; - using reference = T&; - - // ... 迭代器实现 -}; - -```cpp - -使用 `iterator_traits`: - -```cpp - -template -void process(Iter begin, Iter end) { - // 通过traits获取类型 - using traits = std::iterator_traits; - using value_type = typename traits::value_type; - - for (auto it = begin; it != end; ++it) { - value_type temp = *it; // 知道如何存储值 - // 处理... - } -} - -// 对于自定义迭代器 -MyIterator it1, it2; -process(it1, it2); // 使用主模板,从迭代器类型中提取 - -// 对于原生指针 -int arr[10]; -process(arr, arr + 10); // 使用指针偏特化! - -```cpp - -### 嵌入式应用:环形缓冲区迭代器 - -```cpp - -#include - -template -class RingBuffer { -public: - // 迭代器定义 - class iterator { - public: - // 标准迭代器类型定义 - using iterator_category = std::random_access_iterator_tag; - using value_type = T; - using difference_type = std::ptrdiff_t; - using pointer = T*; - using reference = T&; - - iterator(RingBuffer* buf, size_t pos) - : buffer_(buf), pos_(pos) {} - - // 解引用 - reference operator*() const { - return buffer_->data_[pos_ % Size]; - } - - pointer operator->() const { - return &buffer_->data_[pos_ % Size]; - } - - // 算术运算 - iterator& operator++() { - ++pos_; - return *this; - } - - iterator operator++(int) { - iterator tmp = *this; - ++pos_; - return tmp; - } - - iterator& operator--() { - --pos_; - return *this; - } - - iterator operator--(int) { - iterator tmp = *this; - --pos_; - return tmp; - } - - iterator operator+(difference_type n) const { - return iterator(buffer_, pos_ + n); - } - - difference_type operator-(const iterator& other) const { - return pos_ - other.pos_; - } - - // 比较 - bool operator==(const iterator& other) const { - return buffer_ == other.buffer_ && pos_ == other.pos_; - } - - bool operator!=(const iterator& other) const { - return !(*this == other); - } - - private: - RingBuffer* buffer_; - size_t pos_; - }; - - // 构造函数 - RingBuffer() : head_(0), tail_(0), full_(false) {} - - // 元素访问 - bool push(const T& item) { - if (full_) { - return false; // 缓冲区满 - } - data_[tail_] = item; - tail_ = (tail_ + 1) % Size; - full_ = (head_ == tail_); - return true; - } - - bool pop(T& item) { - if (empty()) { - return false; // 缓冲区空 - } - item = data_[head_]; - head_ = (head_ + 1) % Size; - full_ = false; - return true; - } - - // 迭代器接口 - iterator begin() { return iterator(this, head_); } - iterator end() { - return iterator(this, full_ ? tail_ + Size : tail_); - } - - bool empty() const { - return !full_ && (head_ == tail_); - } - - bool full() const { - return full_; - } - -private: - T data_[Size]; - size_t head_; // 读位置 - size_t tail_; // 写位置 - bool full_; -}; - -// 使用示例 -void test_ring_buffer() { - RingBuffer buffer; - - // 添加元素 - for (int i = 0; i < 5; ++i) { - buffer.push(i); - } - - // 使用迭代器遍历 - auto it = buffer.begin(); - auto end = buffer.end(); - - // 使用iterator_traits获取类型 - using traits = std::iterator_traits; - using value_type = typename traits::value_type; - static_assert(std::is_same_v); - - // 算法兼容:可以使用STL算法 - value_type sum = 0; - for (; it != end; ++it) { - sum += *it; - } - - // 或使用标准算法 - RingBuffer buffer2; - for (int i = 0; i < 5; ++i) buffer2.push(i * 2); - - // std::copy兼容! - std::copy(buffer2.begin(), buffer2.end(), - std::ostream_iterator(std::cout, " ")); -} - -```cpp - -### iterator_traits 的完整实现要点 - -实现自己的 iterator_traits 时需要注意: - -| 要点 | 说明 | -|------|------| -| 默认成员类型 | iterator_category, value_type, difference_type, pointer, reference | -| 指针特化 | 必须提供 T*和 const T*的偏特化 | -| value_type选择 | 对于 const T*,value_type 应是 T(去掉const) | -| iterator_category | 指针使用 random_access_iterator_tag | - -### 现代C++的改进(C++20) - -C++20 引入了 `std::iter_reference_t` 等更简洁的别名: - -```cpp - -// C++20风格 -template -using iter_value_t = typename std::iterator_traits::value_type; - -template -using iter_reference_t = typename std::iterator_traits::reference; - -// 使用 -template -void algorithm(Iter begin, Iter end) { - std::iter_value_t sum{}; // 更简洁的语法 -} - -``` - ------- - -## 常见陷阱与解决方案 - -### 陷阱1:特化不匹配导致的无限递归 - -```cpp - -// ❌ 错误:无限递归 -template -struct Traits { - using type = typename Traits::type; // 永远不会到达基础情况 -}; - -// ✅ 正确:提供基础情况 -template -struct Traits { - using type = T; -}; - -template -struct Traits { - using type = typename Traits::type; // 递归但会终止 -}; - -``` - -### 陷阱2:特化顺序依赖 - -```cpp - -// ❌ 不要依赖特化的定义顺序 -template -struct A; - -template -struct A { - using type = T; -}; - -template -struct A { - using type = T; -}; - -// ✅ 主模板总是先定义 -template -struct A { - using type = T; -}; - -template -struct A { - using type = typename A::type; // 可以引用主模板 -}; - -``` - -### 陷阱3:const和引用的正确处理 - -```cpp - -// ❌ 错误:const T& 不能正确匹配 -template -struct RemoveConst { - using type = T; -}; - -template -struct RemoveConst { - using type = T; -}; - -RemoveConst::type; // 还是 const int&,没去掉const! - -// ✅ 正确:先去掉引用,再处理const -template -struct RemoveConstImpl { - using type = T; -}; - -template -struct RemoveConstImpl { - using type = T; -}; - -template -struct RemoveConst { - using type = typename RemoveConstImpl>::type; -}; - -RemoveConst::type; // int,正确! - -``` - -### 陷阱4:函数模板特化与重载混淆 - -```cpp - -// ❌ 错误:想特化但实际上定义了新模板 -template -void f(T*); - -template -void f(T); // 二义性! - -f(42); // 哪个f? - -// ✅ 正确:使用重载 -template -void f(T); - -template -void f(T*); // 重载,不是特化 - -``` - -### 陷阱5:忘记typename关键字 - -```cpp - -template -struct Traits { - using value_type = T; -}; - -template -void func() { - // ❌ 错误:编译器不知道 Traits::value_type 是类型 - Traits::value_type x = 10; - - // ✅ 正确:使用 typename - typename Traits::value_type x = 10; -} - -``` - -**规则**:在模板中访问依赖类型时,必须加上 `typename` 关键字。 - -### 陷阱6:偏特化与SFINAE混用 - -```cpp - -// ❌ 可能有问题:enable_if作为偏特化参数 -template>> -struct OnlyIntegral { - static constexpr bool value = true; -}; - -// 编译错误,而不是替换失败 -OnlyIntegral::value; - -// ✅ 使用 SFINAE 或 Concepts (C++20) -template -struct OnlyIntegral { - static constexpr bool value = false; -}; - -template -struct OnlyIntegral>> { - static constexpr bool value = true; -}; - -// 或 C++20 -template -concept Integral = std::is_integral_v; - -template -struct OnlyIntegral { - static constexpr bool value = true; -}; - -``` - ------- - -## C++20 新特性:Concepts 与 特化 - -### Concepts 简化约束 - -C++20 的 concepts 可以替代部分特化需求: - -```cpp - -// 传统方式:使用SFINAE和特化 -template -struct SupportsIncrement { - static constexpr bool value = false; -}; - -template -struct SupportsIncrement())>> { - static constexpr bool value = true; -}; - -// C++20:使用concept -template -concept SupportsIncrement = requires(T t) { - { ++t } -> std::same_as; -}; - -// 使用 -template -void increment_all(T*begin, T* end) { - for (auto it = begin; it != end; ++it) { - ++(*it); - } -} - -``` - -### requires 子句替代特化 - -```cpp - -// 传统:偏特化 -template -class SmartPtr { - // 通用实现 -}; - -template -class SmartPtr { - // 数组特化 -}; - -// C++20:使用 requires -template -class SmartPtr { - // 通用实现 -}; - -template -requires std::is_array_v -class SmartPtr { - // 数组版本(实际上是偏特化的语法糖) -}; - -``` - -### concept 重载替代函数特化 - -```cpp - -// 传统:函数重载 + SFINAE -template -std::enable_if_t, T> -process(T value) { - return value + 1; -} - -template -std::enable_if_t, T> -process(T value) { - return value * 2.0; -} - -// C++20:concept 重载 -template -T process(T value) { - return value + 1; -} - -template -T process(T value) { - return value * 2.0; -} - -``` - ------- - -## 小结 - -模板特化是C++泛型编程的高级技术,掌握它可以让你的代码更加灵活和高效。 - -### 知识要点总结 - -| 主题 | 核心要点 | -|------|----------| -| 全特化 | 为特定模板参数提供完全独立的实现,使用 `template<>` 语法 | -| 偏特化 | 只有类模板支持,可以部分特化或基于类型属性特化 | -| 匹配规则 | 编译器选择"最特化"的版本,特化程度按参数固定程度排序 | -| 函数模板特化 | 技术上支持全特化,但优先使用重载而非特化 | -| Type Traits | 特化的经典应用,用于在编译期获取类型信息 | - -### 实战建议 - -1. **优先使用偏特化而非全特化**:偏特化可以覆盖一类类型,而不是单个类型 -2. **类模板用特化**:为类模板提供特化是标准做法 -3. **函数模板用重载**:函数特化有诸多限制,重载更灵活 -4. **利用标准库**:`` 已经提供了丰富的 traits,优先使用 -5. **注意递归终止**:在递归特化时确保有基础情况 -6. **使用 typename**:在模板中访问依赖类型时不要忘记 typename - -### 嵌入式最佳实践 - -| 场景 | 推荐做法 | -|------|----------| -| 外设类型封装 | 为指针类型特化,提供寄存器级访问 | -| 缓冲区实现 | 为指针/数组特化,使用零拷贝优化 | -| 序列化 | 使用 traits 区分POD和非POD类型 | -| 中断处理 | 特化提供中断安全的原子操作 | -| 内存管理 | 为固定大小类型特化,避免动态分配 | - -**下一章**,我们将探讨**可变参数模板**,学习如何编写接受任意数量参数的模板,实现类型安全的格式化函数和编译期算法。这是现代C++元编程的核心技术之一。 diff --git a/documents/vol4-advanced/04-if-constexpr.md b/documents/vol4-advanced/04-if-constexpr.md deleted file mode 100644 index 195527c3b..000000000 --- a/documents/vol4-advanced/04-if-constexpr.md +++ /dev/null @@ -1,139 +0,0 @@ ---- -chapter: 4 -cpp_standard: -- 17 -- 20 -description: 详解if constexpr应用 -difficulty: intermediate -order: 4 -platform: host -prerequisites: -- 'Chapter 2: 零开支抽象' -reading_time_minutes: 4 -tags: -- cpp-modern -- host -- intermediate -title: if constexpr 编译期条件 ---- -# `if constexpr`:把编译期分支写得像写注释 —— 工程味实战指南 - -笔者一直认为,在介于最近的现代C++和比较古典的C++98之间,大部分模板编程的使用方式,都是为了组合出特定目的而编写的,这种复杂性有时候并不是我们想要的。比如说,我们在之后学习的模板编程里,很多依赖模板的 `enable_if`、特化、SFINAE 花活本质上只是为了达成我们特定的编译期匹配目的。好在现在,我们有`if constexpr` 来化简绝大多数的场景了。 - -`if constexpr` 是 C++17 带来的一个小而强的工具:把"我们在编译期就能决定的分支"直接交给编译器处理。结果是代码更简洁、模板更易读,都能简化成一段清爽的 `if constexpr`。 - ------- - -## 为什么要关心它 - -- **减少模板特化/重载数量**:把不同类型的行为放在同一个模板体内,通过编译期条件分叉,逻辑集中,维护更方便。 -- **提高可读性**:读代码时看到 `if constexpr` 就知道这是"类型/常量决定"的分支,不用再去找其他特化实现。 -- **避免不必要的实例化错误**:被丢弃(discarded)的分支不会实例化,对某些在某类型下不成立的表达式不会导致编译失败。 -- **性能清晰**:编译器在编译期就能删掉不需要的分支,最终生成的代码像你写了多份特化一样高效。 - ------- - -## `if constexpr (cond)` 怎么用? - -1. `if constexpr (cond)` 要求 `cond` 在编译期可确定(常量表达式)。 -2. 如果 `cond` 为 `true`,编译器仅编译 `then` 分支,`else`(如果有)会被丢弃,不会参与模板实例化(因此其内可能包含对当前类型不合法的代码)。 -3. 反之亦然。 -4. 如果 `cond` 不是编译期常量,会报错(因为 `if constexpr` 语义要求编译期求值)。(PS:现在有时候可以逐步退化为运行期,这个是新东西,所以不同的版本行为稍有不同) - ------- - -## 与普通 `if` 的差别? - -普通 `if` 是运行时判断,两分支都要合法;`if constexpr` 是编译期判断,只有被选择的分支需要合法(对于模板依赖条件尤其重要)。 - ------- - -### Case: 根据类型选择实现 - -场景:打印不同类型的值(工程中常用于日志格式化、序列化分支等)。 - -```cpp -#include -#include - -template -void printValue(const T& v) { - if constexpr (std::is_integral_v) { - std::cout << "整型: " << v << "\n"; - } else if constexpr (std::is_floating_point_v) { - std::cout << "浮点: " << v << "\n"; - } else { - std::cout << "其他类型\n"; - } -} - -// 使用 -// printValue(42); // 输出 整型: 42 -// printValue(3.14); // 输出 浮点: 3.14 - -``` - -优点:只需一个模板即可处理多种类型,逻辑集中,扩展分支也方便。 - ------- - -### Case: 替代 SFINAE / enable_if(把复杂特化变干净) - -场景:为支持 `T::size()` 的类型使用 `.size()`,否则降级为其它实现。 - -```cpp -#include -#include - -// 检测有无 size()(简单版) -template -constexpr bool has_size_v = false; - -template -constexpr bool has_size_v().size())>> = true; - -template -auto getSizeIfPossible(const T& t) { - if constexpr (has_size_v) { - return t.size(); // 只有在 T 有 size() 时才编译 - } else { - return std::size_t{0}; // 备用实现 - } -} - -``` - -说明:如果用传统 SFINAE 或 `enable_if`,需要写多个重载或特化,代码量和维护成本都会上升。 - ------- - -### Case: 编译期递归(`constexpr` + `if constexpr`) - -用法:编译期计算(例如元编程或常量生成)。 - -```cpp -constexpr uint64_t factorial(uint64_t n) { - if constexpr (n <= 1) { - return 1; - } else { - return n * factorial(n - 1); - } -} - -// constexpr auto f6 = factorial(6); // 720,编译期已计算 - -``` - -注意:这里的 `if constexpr` 与 `constexpr` 函数搭配,结果在编译期求值(如果使用场景允许)。 - -## 在线运行 - -在线体验 if constexpr 的类型分派、成员检测和编译期计算: - - diff --git a/documents/vol4-advanced/04-non-type-template-params.md b/documents/vol4-advanced/04-non-type-template-params.md deleted file mode 100644 index 89d491ede..000000000 --- a/documents/vol4-advanced/04-non-type-template-params.md +++ /dev/null @@ -1,1836 +0,0 @@ ---- -chapter: 12 -cpp_standard: -- 11 -- 14 -- 17 -- 20 -description: 深入理解C++非类型模板参数的用法与嵌入式应用 -difficulty: intermediate -order: 4 -platform: host -prerequisites: -- 'Chapter 12: 模板入门概述' -- 'Chapter 12: 函数模板详解' -reading_time_minutes: 53 -tags: -- cpp-modern -- host -- intermediate -title: 非类型模板参数 ---- -# 嵌入式现代C++教程——非类型模板参数 - -## 引言:编译期的魔法数字 - -在嵌入式开发中,我们经常需要处理各种"魔法数字"——寄存器地址、位掩码、缓冲区大小、波特率等。传统做法是使用宏或常量: - -```cpp -#define UART0_BASE 0x4000C000 -#define BUFFER_SIZE 256 -#define BAUD_RATE 115200 - -uint8_t buffer[BUFFER_SIZE]; -``` - -这种方式有几个问题: - -1. **没有类型安全**:宏只是文本替换,编译器不做类型检查 -2. **调试困难**:宏在预处理阶段就被替换,调试时看不到原始名称 -3. **作用域混乱**:宏没有作用域,容易冲突 -4. **无法编译期计算**:不能基于这些值进行复杂的编译期计算 - -C++的非类型模板参数提供了一种更优雅的解决方案。它允许你用**编译期常量**作为模板参数,在保持类型安全的同时,实现零开销的抽象。 - ------- - -## 非类型模板参数基础 - -### 什么是非类型模板参数? - -模板参数不仅可以是类型(`typename T`),还可以是编译期常量值: - -```cpp -template // T是类型参数,N是非类型参数 -class Array { - T data[N]; -}; -``` - -**关键特性**: - -- 非类型模板参数是**编译期常量** -- 它们的值在模板实例化时必须确定 -- 编译器会为不同的值生成不同的模板实例 - -### 支持的类型 - -C++标准限定了非类型模板参数可以使用的类型: - -| C++标准 | 支持的类型 | -|---------|-----------| -| C++11/14 | 整型、枚举、指针、引用、指向成员的指针 | -| C++17 | 增加自动类型推导(`auto`) | -| C++20 | 增加浮点型、类对象(需满足特定条件) | - -```cpp -// 整型 -template -struct IntConstant { }; - -// 枚举 -template -struct EnumConstant { }; - -// 指针 -template -struct PointerConstant { }; - -// 引用 -template -struct ReferenceConstant { }; - -// C++17: auto -template -struct AutoConstant { }; - -// C++20: 浮点型 -template -struct DoubleConstant { }; -``` - ------- - -## 整型非类型模板参数 - -### 基本用法 - -整型是最常用的非类型模板参数类型,包括: - -- `bool`、`char`、`int`、`unsigned int` 等 -- `std::size_t`、`int8_t`、`uint16_t` 等固定宽度类型 - -```cpp -template -struct IntegralConstant { - static constexpr int value = Value; - using value_type = int; - - constexpr operator int() const { return value; } -}; - -// 使用 -using Five = IntegralConstant<5>; -static_assert(Five::value == 5); -``` - -### 编译期数组大小 - -这是非类型模板参数最常见的应用场景: - -```cpp -template -class FixedArray { -public: - constexpr std::size_t size() const { return N; } - constexpr bool empty() const { return N == 0; } - - T& operator[](std::size_t index) { - return data[index]; - } - - const T& operator[](std::size_t index) const { - return data[index]; - } - - T* begin() { return data; } - T* end() { return data + N; } - const T* begin() const { return data; } - const T* end() const { return data + N; } - -private: - T data[N]; -}; - -// 使用 -FixedArray arr1; // 编译期确定大小,栈上分配 -FixedArray arr2; - -static_assert(arr1.size() == 10); -``` - -**优势**: - -- 编译期确定大小,无需动态分配 -- 边界检查可以在编译期进行(配合`static_assert`) -- 内存布局完全可预测 - -### 位掩码生成器 - -在嵌入式开发中,我们经常需要操作寄存器的特定位: - -```cpp -template -struct BitMask { - static_assert(BitPos < sizeof(RegisterType) * 8, "Bit position out of range"); - - static constexpr RegisterType mask = static_cast(1) << BitPos; - - // 设置位 - static constexpr RegisterType set(RegisterType reg) { - return reg | mask; - } - - // 清除位 - static constexpr RegisterType clear(RegisterType reg) { - return reg & ~mask; - } - - // 切换位 - static constexpr RegisterType toggle(RegisterType reg) { - return reg ^ mask; - } - - // 测试位 - static constexpr bool is_set(RegisterType reg) { - return (reg & mask) != 0; - } -}; - -// 使用场景:GPIO配置 -using GPIO_Pin5 = BitMask; -using GPIO_Pin13 = BitMask; - -uint32_t gpio_mode = 0; -gpio_mode = GPIO_Pin5::set(gpio_mode); // 设置第5位 -gpio_mode = GPIO_Pin13::clear(gpio_mode); // 清除第13位 - -if (GPIO_Pin5::is_set(gpio_mode)) { - // 第5位已设置 -} -``` - -### 多位掩码 - -```cpp -template -struct BitFieldMask { - static_assert(HighBit >= LowBit, "High bit must be >= Low bit"); - static_assert(HighBit < sizeof(RegisterType) * 8, "High bit out of range"); - - static constexpr RegisterType width = HighBit - LowBit + 1; - static constexpr RegisterType mask = ((static_cast(1) << width) - 1) << LowBit; - - // 提取位域 - static constexpr RegisterType extract(RegisterType reg) { - return (reg & mask) >> LowBit; - } - - // 设置位域 - static constexpr RegisterType set(RegisterType reg, RegisterType value) { - return (reg & ~mask) | ((value << LowBit) & mask); - } -}; - -// 使用:UART波特率设置(通常在特定位域) -using UART_BaudField = BitFieldMask; - -uint32_t uart_ctrl = 0; -uart_ctrl = UART_BaudField::set(uart_ctrl, 115200); // 设置波特率 -``` - -### 编译期查找表 - -```cpp -template -class LookupTable { -public: - constexpr LookupTable(const T (&values)[Size]) { - for (std::size_t i = 0; i < Size; ++i) { - data[i] = values[i]; - } - } - - constexpr T operator[](std::size_t index) const { - return data[index]; - } - - constexpr std::size_t size() const { return Size; } - -private: - T data[Size]; -}; - -// 编译期生成正弦表 -constexpr double generate_sin(std::size_t i, std::size_t total) { - return std::sin(2.0 * 3.14159265358979323846 * i / total); -} - -template -struct SineTable { - constexpr SineTable() : values{} { - for (std::size_t i = 0; i < Size; ++i) { - values[i] = generate_sin(i, Size); - } - } - - double values[Size]; -}; - -constexpr SineTable<256> sine_table; // 编译期生成256点正弦表 -``` - -### 限制与注意事项 - -**整型限制**: - -1. **必须是编译期常量** - - ```cpp - constexpr int N = 10; - FixedArray arr1; // OK - - int M = 20; - FixedArray arr2; // 错误:M不是编译期常量 - ``` - -2. **大小限制** - - ```cpp - template - class LargeBuffer { - uint8_t data[N]; // N不能太大,否则栈溢出 - }; - - LargeBuffer<1024> buf1; // OK - LargeBuffer<1024*1024> buf2; // 可能栈溢出 - ``` - -3. **符号问题** - - ```cpp - template // 有符号 - struct SignedBuf { }; - - template // 无符号 - struct UnsignedBuf { }; - - SignedBuf<-1> sb; // OK - // UnsignedBuf<-1> ub; // 错误:无符号不能为负 - ``` - ------- - -## 指针和引用类型参数 - -### 指针类型参数 - -指针类型参数允许你在编译期绑定到全局对象: - -```cpp -// 全局数组 -constexpr char device_name[] = "STM32F407"; - -template -struct Device { - static constexpr const char* get_name() { return Name; } -}; - -// 使用 -using MyDevice = Device; -static_assert(std::string_view{MyDevice::get_name()} == "STM32F407"); -``` - -**注意**:指针参数必须指向具有外部链接的对象(全局/静态变量)。 - -### 更实用的指针参数场景:寄存器地址映射 - -这是非类型模板参数在嵌入式中最重要的应用之一: - -```cpp -// 寄存器基类 -template -struct Register { - using value_type = RegType; - - // 读寄存器 - static RegType read() { - return *reinterpret_cast(Address); - } - - // 写寄存器 - static void write(RegType value) { - *reinterpret_cast(Address) = value; - } - - // 设置位 - static void set(RegType mask) { - *reinterpret_cast(Address) |= mask; - } - - // 清除位 - static void clear(RegType mask) { - *reinterpret_cast(Address) &= ~mask; - } - - // 地址访问 - static constexpr uintptr_t address() { return Address; } -}; - -// STM32 GPIO寄存器定义 -using GPIOA_BASE = Register; -using GPIOB_BASE = Register; - -// GPIO模式寄存器偏移 -template -struct GPIO_MODER : public Register { - using BaseReg = Base; - static constexpr uintptr_t offset = Offset; -}; - -// 使用 -using GPIOA_MODER = GPIO_MODER; - -// 配置PA5为输出(GPIO模式寄存器中第10-11位) -void config_pa5_output() { - constexpr uint32_t mode_mask = 0x3 << 10; // PA5对应位 - constexpr uint32_t output_mode = 0x1 << 10; - uint32_t current = GPIOA_MODER::read(); - GPIOA_MODER::write((current & ~mode_mask) | output_mode); -} -``` - -### 引用类型参数 - -引用类型参数与指针类似,但提供了更自然的语法: - -```cpp -constexpr int global_config = 42; - -template -struct ConfigReader { - static int get() { return Config; } - static void set(int value) { Config = value; } -}; - -// 使用 -using MyConfig = ConfigReader; -``` - -### 指向成员的指针 - -虽然较少使用,但有时也很有用: - -```cpp -struct SensorData { - int temperature; - int humidity; - int pressure; -}; - -template -struct SensorField { - static int read(const SensorData& data) { - return data.*Member; - } - - static void write(SensorData& data, int value) { - data.*Member = value; - } -}; - -// 使用 -using TempField = SensorField<&SensorData::temperature>; -using HumidField = SensorField<&SensorData::humidity>; - -SensorData data{}; -TempField::write(data, 25); // 设置温度 -``` - -### 限制 - -**指针/引用参数的限制**: - -1. **必须指向/引用全局/静态对象** - - ```cpp - template - struct PtrHolder { }; - - int global_var; - static int static_var; - - void func() { - int local_var; - PtrHolder<&global_var> h1; // OK - PtrHolder<&static_var> h2; // OK - // PtrHolder<&local_var> h3; // 错误:局部变量 - } - ``` - -2. **字符串字面量的特殊性** - - ```cpp - template - struct StringHolder { }; - - // 字符串字面量具有内部链接,C++中需要特殊处理 - extern constexpr char str[] = "hello"; // extern赋予外部链接 - StringHolder sh; // OK - ``` - -3. **nullptr的特殊处理** - - ```cpp - template - struct PtrTest { }; - - // PtrTest pt; // 错误:nullptr不能直接用于指针模板参数 - // 正确做法:使用0或nullptr_t模板参数 - template - struct NullPtrTest { }; - - NullPtrTest npt; // OK - ``` - ------- - -## C++17:auto非类型模板参数 - -### 基本语法 - -C++17引入了`auto`作为非类型模板参数类型,让编译器自动推导: - -```cpp -template -struct AutoConstant { - static constexpr auto value = Value; - using value_type = decltype(Value); -}; - -// 使用 -using IntVal = AutoConstant<42>; // 推导为int -using CharVal = AutoConstant<'x'>; // 推导为char -using UintVal = AutoConstant<123U>; // 推导为unsigned int -using PtrVal = AutoConstant; // 推导为std::nullptr_t -``` - -### 类型推导规则 - -```cpp -template -void print_value() { - std::cout << Value << " (type: " << typeid(decltype(Value)).name() << ")\n"; -} - -print_value<42>(); // int -print_value<42L>(); // long -print_value<42U>(); // unsigned int -print_value<3.14>(); // double -print_value<'a'>(); // char -``` - -### 实际应用:通用值包装器 - -```cpp -template -struct ValueHolder { - constexpr static auto value = Value; - constexpr operator decltype(Value)() const { return Value; } - - // 基于值类型的操作 - constexpr auto square() const { - return Value * Value; - } -}; - -// 使用 -constexpr auto v1 = ValueHolder<10>{}; -static_assert(v1.value == 10); -static_assert(v1.square() == 100); -``` - -### 类型安全的枚举包装 - -```cpp -enum class Color : uint8_t { - Red = 0, - Green = 1, - Blue = 2 -}; - -template -struct ColorInfo { - static_assert(std::is_same_v, - "Must be a Color enum value"); - - static constexpr Color color = ColorValue; - static constexpr const char* name() { - if constexpr (color == Color::Red) return "Red"; - else if constexpr (color == Color::Green) return "Green"; - else if constexpr (color == Color::Blue) return "Blue"; - else return "Unknown"; - } -}; - -// 使用 -using RedColor = ColorInfo; -static_assert(std::string_view{RedColor::name()} == "Red"); -``` - -### 编译期值序列 - -```cpp -template -struct ValueSequence { - static constexpr std::size_t size = sizeof...(Values); - - template - static constexpr auto get = std::get(std::make_tuple(Values...)); -}; - -// 使用 -using MySequence = ValueSequence<1, 2.0, 'c', nullptr>; -static_assert(MySequence::size == 4); -static_assert(MySequence::get<0> == 1); -``` - -### auto参数的限制 - -```cpp -// ❌ 不能作为函数参数的默认模板参数 -template // C++17允许,但使用时需要注意 -struct DefaultAuto { }; - -DefaultAuto<> da; // OK - -// ❌ 不能推导复杂的类型 -struct MyType { int x; }; -// template // 错误:非字面量类型 -// struct ComplexAuto { }; - -// ✅ C++20后,字面量类类型也可以使用 -template -struct LiteralAuto { }; - -// C++20允许,如果MyType是字面量类型 -struct MyType { - int x; - constexpr MyType(int v) : x(v) {} -}; - -// LiteralAuto la; // C++20: OK -``` - ------- - -## 实战:编译期数组大小工具 - -### 问题背景 - -在C/C++中,数组作为函数参数会退化为指针,丢失大小信息: - -```cpp -void func(int arr[]) { - // sizeof(arr)是指针大小,不是数组大小! - // 无法获取原始数组大小 -} - -int data[100]; -func(data); // 丢失大小信息 -``` - -传统C宏的做法存在安全问题: - -```cpp -#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) - -// 问题:对指针使用会得到错误结果 -int* ptr = nullptr; -size_t wrong = ARRAY_SIZE(ptr); // 编译通过,但结果错误 -``` - -### 模板解决方案 - -```cpp -// 基础版本 -template -constexpr std::size_t array_size(T (&)[N]) noexcept { - return N; -} - -// 使用 -int data[42]; -constexpr auto size = array_size(data); // 42,编译期常量 - -// array_size(ptr); // 编译错误!指针不能匹配 -``` - -### 增强版:类型安全的数组工具 - -```cpp -template -struct ArrayInfo { - using value_type = T; - using pointer = T*; - using const_pointer = const T*; - using reference = T&; - using const_reference = const T&; - using size_type = std::size_t; - - static constexpr size_type size = N; - static constexpr size_type bytes = sizeof(T) * N; - - // 编译期边界检查 - template - static constexpr size_type safe_index = Index < N ? Index : - throw "Index out of bounds"; // 编译期错误 - - // 元素访问(编译期索引) - template - static constexpr reference get(T (&arr)[N]) { - static_assert(Index < N, "Index out of bounds"); - return arr[Index]; - } - - // 字节大小 - static constexpr size_type byte_size() noexcept { - return bytes; - } -}; - -// 使用 -int data[10]; -using Info = ArrayInfo; - -static_assert(Info::size == 10); -static_assert(Info::bytes == 40); -static_assert(Info::safe_index<5> == 5); -// static_assert(Info::safe_index<15> == 15); // 编译错误 -``` - -### 多维数组支持 - -```cpp -// 一维数组 -template -constexpr std::size_t array_size(T (&)[N]) noexcept { - return N; -} - -// 二维数组 -template -constexpr std::size_t array_size(T (&)[M][N]) noexcept { - return M * N; -} - -// 通用版:返回总元素数 -template -constexpr std::size_t array_total_size(T (&)[N]) noexcept { - return N; -} - -template -constexpr std::size_t array_total_size(T (&)[M][Ns...]) noexcept { - return M * array_total_size(((T(&)[Ns...])nullptr)[0]); -} - -// 获取维度 -template -struct ArrayRank; - -template -struct ArrayRank : std::integral_constant {}; - -template -struct ArrayRank : std::integral_constant::value> {}; - -// 使用 -int arr2d[3][4]; -static_assert(array_size(arr2d) == 3 * 4); // 总元素数 -static_assert(ArrayRank::value == 2); // 维度 -``` - -### 编译期数组迭代器 - -```cpp -template -class ArrayIterator { -public: - constexpr ArrayIterator(T (&arr)[N], std::size_t pos = 0) - : array_(arr), pos_(pos) {} - - constexpr T& operator*() const { - return array_[pos_]; - } - - constexpr ArrayIterator& operator++() { - ++pos_; - return *this; - } - - constexpr ArrayIterator operator++(int) { - auto tmp = *this; - ++pos_; - return tmp; - } - - constexpr bool operator!=(const ArrayIterator& other) const { - return pos_ != other.pos_; - } - -private: - T (&array_)[N]; - std::size_t pos_; -}; - -template -struct ArrayRange { - T (&array)[N]; - - constexpr ArrayIterator begin() const { - return {array, 0}; - } - - constexpr ArrayIterator end() const { - return {array, N}; - } -}; - -// 使用 -int data[5] = {1, 2, 3, 4, 5}; -constexpr auto range = ArrayRange{data}; - -// 编译期遍历 -constexpr bool all_positive = [] { - for (auto it = range.begin(); it != range.end(); ++it) { - if (*it <= 0) return false; - } - return true; -}(); -static_assert(all_positive); -``` - -### 完整示例:嵌入式缓冲区工具 - -::: details 点击展开完整的编译期缓冲区工具代码 - -```cpp -#include -#include - -// 基础工具 -template -constexpr std::size_t array_size(T (&)[N]) noexcept { - return N; -} - -// 编译期缓冲区类 -template -class AlignedBuffer { - alignas(Alignment) T data_[Size]; -public: - using value_type = T; - using size_type = std::size_t; - using pointer = T*; - using const_pointer = const T*; - - static constexpr size_type size = Size; - static constexpr size_type alignment = Alignment; - static constexpr size_type bytes = sizeof(T) * Size; - - // 编译期初始化为特定值 - constexpr AlignedBuffer() : data_{} {} - - constexpr AlignedBuffer(T init_value) : data_{} { - for (size_type i = 0; i < Size; ++i) { - data_[i] = init_value; - } - } - - // 元素访问 - constexpr T& operator[](size_type index) { - return data_[index]; - } - - constexpr const T& operator[](size_type index) const { - return data_[index]; - } - - // 指针访问 - constexpr pointer data() { return data_; } - constexpr const_pointer data() const { return data_; } - - // 填充 - constexpr void fill(T value) { - for (size_type i = 0; i < Size; ++i) { - data_[i] = value; - } - } - - // 比较编译期大小 - template - constexpr bool size_equals() const { - return Size == OtherSize; - } -}; - -// 环形缓冲区(编译期容量) -template -class CircularBuffer { - T data_[Capacity]; - std::size_t head_ = 0; - std::size_t tail_ = 0; - bool full_ = false; - -public: - static constexpr std::size_t capacity = Capacity; - using value_type = T; - - constexpr CircularBuffer() : data_{} {} - - constexpr bool push(T value) { - data_[head_] = value; - if (full_) { - tail_ = (tail_ + 1) % Capacity; - } - head_ = (head_ + 1) % Capacity; - full_ = (head_ == tail_); - return true; - } - - constexpr bool pop(T& value) { - if (empty()) return false; - value = data_[tail_]; - full_ = false; - tail_ = (tail_ + 1) % Capacity; - return true; - } - - constexpr bool empty() const { - return !full_ && (head_ == tail_); - } - - constexpr bool full() const { - return full_; - } - - constexpr std::size_t size() const { - if (full_) return Capacity; - return (head_ >= tail_) ? (head_ - tail_) : - (Capacity + head_ - tail_); - } - - constexpr void clear() { - head_ = tail_ = 0; - full_ = false; - } -}; - -// 使用示例 -void buffer_example() { - // 对齐缓冲区 - AlignedBuffer dma_buffer; // 16字节对齐,用于DMA - AlignedBuffer uart_rx; - - // 环形缓冲区 - CircularBuffer rx_ring; - rx_ring.push(0x01); - rx_ring.push(0x02); - - uint8_t data; - while (rx_ring.pop(data)) { - // 处理数据 - } -} - -``` - -::: - ------- - -## 实战:位掩码生成器 - -### 编译期位操作工具集 - -```cpp - -#include - -namespace bits { - -// 基础位掩码 -template -struct Bit { - static_assert(Bit < sizeof(RegType) * 8, "Bit out of range"); - static constexpr RegType mask = RegType{1} << Bit; - - static constexpr RegType set(RegType reg) { return reg | mask; } - static constexpr RegType clear(RegType reg) { return reg & ~mask; } - static constexpr RegType toggle(RegType reg) { return reg ^ mask; } - static constexpr bool is_set(RegType reg) { return (reg & mask) != 0; } -}; - -// 多位掩码 -template -struct Bits { - static_assert(High >= Low, "High must be >= Low"); - static constexpr RegType width = High - Low + 1; - static constexpr RegType mask = ((RegType{1} << width) - 1) << Low; - - static constexpr RegType extract(RegType reg) { - return (reg & mask) >> Low; - } - - static constexpr RegType set(RegType reg, RegType value) { - return (reg & ~mask) | ((value << Low) & mask); - } -}; - -// 编译期计算位掩码值 -template -struct BitMask { - static constexpr RegType value = ((RegType{1} << Bits) | ...); -}; - -// 寄存器定义辅助 -template -struct RegisterField { - static constexpr RegType offset = Offset; - static constexpr RegType mask = ((RegType{1} << Bits) | ...); - - template - static constexpr RegType read() { - return *reinterpret_cast(BaseAddr::address + offset); - } - - template - static constexpr void write(RegType value) { - *reinterpret_cast(BaseAddr::address + offset) = value; - } - - template - static constexpr void set(RegType value) { - write(read() | (value << offset)); - } -}; - -} // namespace bits - -// 使用示例 -using namespace bits; - -// 定义LED位掩码 -using LED1 = Bit; -using LED2 = Bit; -using LED3 = Bit; - -void gpio_example() { - uint32_t gpio_odr = 0; - - // 设置LED - gpio_odr = LED1::set(gpio_odr); - gpio_odr = LED2::set(gpio_odr); - - // 测试LED状态 - if (LED1::is_set(gpio_odr)) { - // LED1打开 - } - - // 切换LED - gpio_odr = LED3::toggle(gpio_odr); - - // 清除LED - gpio_odr = LED2::clear(gpio_odr); -} -``` - -### 编译期位域提取器 - -```cpp -// 通信协议中的位域解析 -template -struct BitField { - static_assert(BitWidth > 0 && BitWidth <= 64, "Invalid bit width"); - static_assert(BitOffset >= 0 && BitOffset < 8, "Invalid bit offset"); - - using value_type = std::conditional_t>>; - - static constexpr int total_bit_offset = ByteOffset * 8 + BitOffset; - static constexpr T mask = ((T{1} << BitWidth) - 1) << total_bit_offset; - - static constexpr value_type extract(const T* buffer) { - return static_cast( - (buffer[ByteOffset] >> BitOffset) & ((1 << BitWidth) - 1) - ); - } - - static constexpr void encode(T* buffer, value_type value) { - buffer[ByteOffset] &= ~(((1 << BitWidth) - 1) << BitOffset); - buffer[ByteOffset] |= (value & ((1 << BitWidth) - 1)) << BitOffset; - } -}; - -// 使用:CAN消息解析 -uint8_t can_msg[8]; - -// 假设某信号位于字节2的第3位,长度5位 -using CanSignal = BitField; - -auto signal_value = CanSignal::extract(can_msg); // 提取信号 -CanSignal::encode(can_msg, 0x1F); // 编码信号 -``` - -### SPI/I2C配置生成器 - -```cpp -// SPI配置参数 -template -struct SPIConfig { - static_assert(Prescaler >= 2 && Prescaler <= 256, "Invalid prescaler"); - static_assert(CPOL == 0 || CPOL == 1, "CPOL must be 0 or 1"); - static_assert(CPHA == 0 || CPHA == 1, "CPHA must be 0 or 1"); - static_assert(BitOrder == 0 || BitOrder == 1, "BitOrder must be MSB_FIRST(0) or LSB_FIRST(1)"); - - // 计算控制寄存器值(简化版) - static constexpr uint32_t cr1_value = - (BitOrder << 7) | - (CPHA << 0) | - (CPOL << 1) | - ((Prescaler / 2 - 1) << 3); // 假设prescaler编码 -}; - -// 预定义配置 -using SPI_FullSpeed_CPOL0_CPHA0 = SPIConfig<2, 0, 0, 0>; -using SPI_HalfSpeed_CPOL1_CPHA1 = SPIConfig<4, 1, 1, 0>; - -// I2C速率配置 -template -struct I2CTiming { - static constexpr int standard_freq = 100000; // 100kHz - static constexpr int fast_freq = 400000; // 400kHz - static constexpr int fast_plus_freq = 1000000; // 1MHz - - static constexpr bool is_standard = ClockRate == standard_freq; - static constexpr bool is_fast = ClockRate == fast_freq; - static constexpr bool is_fast_plus = ClockRate == fast_plus_freq; - - // 计算时序寄存器值(简化) - static constexpr uint32_t timingr_value = - (is_standard ? 0x00000000 : is_fast ? 0x00600000 : 0x00100000); -}; - -using I2C_StandardMode = I2CTiming<100000, 150, 50>; -using I2C_FastMode = I2CTiming<400000, 100, 30>; -``` - ------- - -## 嵌入式应用:寄存器地址的编译期封装 - -### 类型安全的寄存器访问 - -```cpp -#include -#include - -namespace hal { - -// 寄存器地址标记类型 -template -struct RegisterAddress { - uintptr_t address; - constexpr explicit RegisterAddress(uintptr_t addr) : address(addr) {} -}; - -// 寄存器基类 -template -class Register { -public: - using value_type = RegType; - - explicit constexpr Register(RegisterAddress addr) - : address_(reinterpret_cast(addr.address)) {} - - // 读操作 - constexpr RegType read() const volatile { - return *address_; - } - - // 写操作 - constexpr void write(RegType value) volatile { - *address_ = value; - } - - // 设置位 - constexpr void set(RegType mask) volatile { - *address_ |= mask; - } - - // 清除位 - constexpr void clear(RegType mask) volatile { - *address_ &= ~mask; - } - - // 切换位 - constexpr void toggle(RegType mask) volatile { - *address_ ^= mask; - } - - // 读-修改-写 - constexpr void modify(RegType clear_mask, RegType set_mask) volatile { - *address_ = (*address_ & ~clear_mask) | set_mask; - } - -private: - volatile RegType* const address_; -}; - -// 标签类型(防止类型混淆) -struct GPIOA_Tag {}; -struct GPIOB_Tag {}; -struct UART1_Tag {}; -struct SPI1_Tag {}; - -// 基地址定义 -namespace base { - constexpr inline RegisterAddress GPIOA{0x40020000}; - constexpr inline RegisterAddress GPIOB{0x40020400}; - constexpr inline RegisterAddress UART1{0x40011000}; - constexpr inline RegisterAddress SPI1{0x40013000}; -} - -// 具体寄存器定义(使用偏移量) -template -using GPIO_Register = Register; - -} // namespace hal -``` - -### 外设定义框架 - -```cpp -namespace stm32f4 { - -// GPIO端口定义 -template -struct GPIO_Port { - using MODER = Register{0x40020000}>; - using OTYPER = Register{0x40020004}>; - using OSPEEDR = Register{0x40020008}>; - using PUPDR = Register{0x4002000C}>; - using IDR = Register{0x40020010}>; - using ODR = Register{0x40020014}>; - using BSRR = Register{0x40020018}>; - - // 模式定义 - enum Mode : uint32_t { - Input = 0, - Output = 1, - Alternate = 2, - Analog = 3 - }; - - // 输出类型 - enum OutputType : uint16_t { - PushPull = 0, - OpenDrain = 1 - }; - - // 速度 - enum Speed : uint32_t { - Low = 0, - Medium = 1, - High = 2, - VeryHigh = 3 - }; - - // 上下拉 - enum Pull : uint32_t { - None = 0, - Up = 1, - Down = 2 - }; - - // 引脚配置辅助函数 - template - static void config_mode(Mode mode) { - constexpr uint32_t mask = 0x3 << (Pin * 2); - MODER reg{RegisterAddress{0x40020000}}; - reg.modify(mask, static_cast(mode) << (Pin * 2)); - } - - template - static void config_output(OutputType type) { - OTYPER reg{RegisterAddress{0x40020004}}; - if (type == OpenDrain) { - reg.set(1 << Pin); - } else { - reg.clear(1 << Pin); - } - } -}; - -// 具体端口类型别名 -using GPIOA = GPIO_Port; -using GPIOB = GPIO_Port; - -} // namespace stm32f4 - -// 使用示例 -void gpio_init() { - using namespace stm32f4; - - // 配置PA5为输出(板载LED) - GPIOA::config_mode<5>(GPIOA::Output); - GPIOA::config_output<5>(GPIOA::PushPull); - - // 控制LED - GPIOA::ODR odr{RegisterAddress{0x40020014}}; - odr.set(1 << 5); // 点亮 - odr.clear(1 << 5); // 熄灭 -} -``` - -### UART配置示例 - -```cpp -namespace stm32f4 { - -template -struct UART { - using CR1 = Register{0x0}>; - using CR2 = Register{0x4}>; - using BRR = Register{0x8}>; - - // 波特率计算(简化版) - template - static constexpr uint32_t calculate_brr() { - return (ClockFreq + BaudRate / 2) / BaudRate; - } - - // 编译期配置 - template - static void config() { - constexpr uint32_t brr_value = calculate_brr(); - - BRR brr{RegisterAddress{0x40011000 + 0x8}}; - brr.write(brr_value); - - CR1 cr1{RegisterAddress{0x40011000}}; - uint32_t cr1_value = (1 << 13); // UE: UART使能 - cr1_value |= (1 << 2); // RE: 接收使能 - cr1_value |= (1 << 3); // TE: 发送使能 - - if constexpr (EnableParity) { - cr1_value |= (1 << 10); // PCE: 奇偶校验使能 - if constexpr (!Even) { - cr1_value |= (1 << 9); // PS: 奇校验 - } - } - - cr1.write(cr1_value); - } -}; - -using UART1 = UART; - -} // namespace stm32f4 - -// 使用:在编译期配置UART -void uart_init() { - // 假设APB2时钟84MHz,配置为115200波特率 - // 这些值都在编译期计算 - stm32f4::UART1::config<84000000, 115200>(); -} -``` - -### 编译期引脚映射 - -```cpp -// 引脚映射配置 -template -struct Pin { - static constexpr int port = Port; - static constexpr int pin = Pin; - - // 编译期生成引脚号 - static constexpr int pin_number = Port * 16 + Pin; - - // 编译期生成位掩码 - static constexpr uint32_t mask = 1 << Pin; -}; - -// 引脚别名 -using PA5 = Pin<0, 5>; // GPIOA, Pin 5 -using PB13 = Pin<1, 13>; // GPIOB, Pin 13 -using PC9 = Pin<2, 9>; // GPIOC, Pin 9 - -// LED定义 -using LED = PA5; - -// 编译期检查 -static_assert(LED::pin_number == 5); -static_assert(LED::mask == 0x20); - -// 引脚功能配置 -template -struct AlternateFunction { - static_assert(Pin::pin < 16, "Invalid pin number"); - - // AFR寄存器:pin 0-7使用AFRL,pin 8-15使用AFRH - static constexpr bool use_high = Pin::pin >= 8; - static constexpr int reg_offset = use_high ? 4 : 0; - static constexpr int shift = (Pin::pin % 8) * 4; - static constexpr uint32_t mask = 0xF << shift; - - static constexpr uint32_t afr_value = static_cast(AlternateFunc::value) << shift; -}; - -// SPI引脚配置示例 -enum class SPI_AF : uint32_t { - AF5 = 5, // SPI1/2 - AF6 = 6 // SPI3 -}; - -using SPI1_SCK = AlternateFunction, SPI_AF::AF5>; // PA5 -> AF5 -using SPI1_MISO = AlternateFunction, SPI_AF::AF5>; // PA6 -> AF5 -using SPI1_MOSI = AlternateFunction, SPI_AF::AF5>; // PA7 -> AF5 -``` - -### 完整示例:DMA配置 - -::: details 点击展开DMA编译期配置示例 - -```cpp -namespace stm32f4 { - -// DMA通道定义 -template -struct DMA { - static_assert(Stream >= 0 && Stream < 8, "Invalid DMA stream"); - static_assert(Channel >= 0 && Channel < 8, "Invalid DMA channel"); - - static constexpr uintptr_t base = 0x40026000; - static constexpr uintptr_t stream_base = base + Stream * 0x10 + 0x10 * (Stream / 4); - - using CR = Register{stream_base + 0x00}>; - using NDTR = Register{stream_base + 0x04}>; - using PAR = Register{stream_base + 0x08}>; - using M0AR = Register{stream_base + 0x0C}>; - using M1AR = Register{stream_base + 0x10}>; - - // 方向 - enum Direction : uint32_t { - PeripheralToMemory = 0, - MemoryToPeripheral = 1, - MemoryToMemory = 2 - }; - - // 数据宽度 - enum Width : uint32_t { - Byte = 0, - HalfWord = 1, - Word = 2 - }; - - // 编译期配置 - template< - Direction Dir, - Width MemWidth = Word, - Width PeriphWidth = Word, - bool MemInc = true, - bool PeriphInc = false, - bool Circular = false - > - static void config() { - constexpr uint32_t cr_value = - (Channel << 25) | // 通道选择 - (Dir << 6) | // 方向 - (MemInc << 9) | // 内存递增 - (PeriphInc << 8) | // 外设递增 - (MemWidth << 10) | // 内存宽度 - (PeriphWidth << 8) | // 外设宽度 - (Circular << 5); // 循环模式 - - CR cr{RegisterAddress{stream_base}}; - cr.write(cr_value); - } - - // 启动传输 - static void enable() { - CR cr{RegisterAddress{stream_base}}; - cr.set(1 << 0); // EN位 - } - - static void disable() { - CR cr{RegisterAddress{stream_base}}; - cr.clear(1 << 0); - } -}; - -// 预定义DMA配置 -using UART1_TX_DMA = DMA<4, 4>; // Stream 4, Channel 4 - -void uart_dma_send_example() { - uint8_t tx_buffer[256]; - uintptr_t uart1_dr = 0x40011004; - - // 配置DMA - UART1_TX_DMA::config< - DMA<4, 4>::MemoryToPeripheral, - DMA<4, 4>::Byte, - DMA<4, 4>::Byte, - true, false - >(); - - // 设置地址 - UART1_TX_DMA::NDTR ndtr{RegisterAddress{UART1_TX_DMA::stream_base + 0x04}}; - ndtr.write(256); - - UART1_TX_DMA::PAR par{RegisterAddress{UART1_TX_DMA::stream_base + 0x08}}; - par.write(uart1_dr); - - UART1_TX_DMA::M0AR m0ar{RegisterAddress{UART1_TX_DMA::stream_base + 0x0C}}; - m0ar.write(reinterpret_cast(tx_buffer)); - - // 启动 - UART1_TX_DMA::enable(); -} - -} // namespace stm32f4 - -``` - -::: - ------- - -## 常见陷阱与解决方案 - -### 陷阱1:浮点类型作为非类型模板参数 - -**问题**:C++17之前不支持浮点类型的非类型模板参数: - -```cpp - -// C++17之前:错误 -template -struct DoubleConstant { }; - -// C++17之前的解决方案:转换为整数比例 -template -struct Ratio { - static constexpr double value = static_cast(Numerator) / Denominator; -}; - -using PiApprox = Ratio<314159, 100000>; // 3.14159 -``` - -**C++20解决方案**: - -```cpp -// C++20:直接支持浮点类型 -template -struct DoubleConstant { - static constexpr double value = Value; -}; - -using Pi = DoubleConstant<3.14159265358979323846>; -``` - -### 陷阱2:类对象作为非类型模板参数 - -**C++20之前**:不能使用类对象作为非类型模板参数: - -```cpp -// ❌ C++17错误 -struct Config { - int value; - Config(int v) : value(v) {} -}; - -template -struct ConfigHolder { }; -``` - -**C++20解决方案**:需要类类型是"字面类型": - -```cpp -// ✅ C++20:字面类型 -struct Config { - int value; - constexpr Config(int v) : value(v) {} -}; - -// constexpr变量可以作为模板参数 -constexpr Config my_config{42}; - -template -struct ConfigHolder { - static constexpr int config_value = C.value; -}; - -using MyHolder = ConfigHolder; -static_assert(MyHolder::config_value == 42); -``` - -### 陷阱3:不同模板参数类型导致重复实例化 - -```cpp -template -struct Buffer { - uint8_t data[N]; -}; - -Buffer<10> b1; // 实例化1 -Buffer<010> b2; // 实例化2(八进制10 = 十进制8)! -Buffer<0xA> b3; // 错误:非整型字面量 -Buffer<10> b4; // 与b1共享实例 - -// 解决方案:使用强类型包装 -template -struct Size { - static constexpr std::size_t value = N; -}; - -template -struct TypedBuffer { - uint8_t data[S::value]; -}; - -using Size10 = Size<10>; -TypedBuffer tb1; -TypedBuffer> tb2; // 同一类型 -``` - -### 陷阱4:编译期常量与运行时值的混淆 - -```cpp -template -void process(int (&arr)[N]) { - // N是编译期常量 - std::array arr_copy; // OK - - std::size_t runtime_size = get_size(); - // std::array arr2; // 错误:需要编译期常量 -} - -// 解决方案:使用constexpr函数返回编译期值 -constexpr std::size_t get_buffer_size() { - return 256; -} - -std::array buffer; // OK -``` - -### 陷阱5:链接性问题 - -```cpp -// 头文件 A.h -template -struct A { }; - -// 源文件 a.cpp -const char hello[] = "hello"; - -// 头文件 B.h -template -struct B { }; - -// 问题:字符串字面量具有内部链接 -// template struct C { }; -// C<"hello"> c; // 错误:某些编译器不允许 - -// 解决方案:使用extern -extern constexpr char world[] = "world"; -A a; // OK -``` - -### 陷阱6:模板参数依赖的名称查找 - -```cpp -template -struct Factorial { - static constexpr int value = N * Factorial::value; -}; - -template<> -struct Factorial<0> { - static constexpr int value = 1; -}; - -// 问题:某些编译器需要显式指定模板参数 -template -struct Factorial2 { - static constexpr int value = N * ::Factorial2::value; // 加::限定 -}; - -// 更好的解决方案:使用别名模板 -template -using Factor = Factorial; - -constexpr int result = Factor<5>::value; -``` - ------- - -## C++20新特性 - -### 浮点非类型模板参数 - -```cpp -template -struct DoubleParam { - static constexpr double value = Value; - constexpr double operator()() const { return Value; } -}; - -// 使用 -constexpr DoubleParam<3.14159> pi; -constexpr auto pi_value = pi(); -static_assert(pi_value > 3.14 && pi_value < 3.15); - -// 应用:编译期三角函数 -template -struct SinTable { - static constexpr double value = std::sin(Angle); -}; - -constexpr auto sin_30 = SinTable<3.14159265358979323846 / 6>::value; -static_assert(sin_30 > 0.49 && sin_30 < 0.51); -``` - -### 字面类型非类型模板参数 - -```cpp -struct Point { - int x, y; - constexpr Point(int x_, int y_) : x(x_), y(y_) {} - constexpr bool operator==(const Point& other) const { - return x == other.x && y == other.y; - } -}; - -template -struct PointHolder { - static constexpr int x = P.x; - static constexpr int y = P.y; -}; - -// 使用 -constexpr Point origin{0, 0}; -using Origin = PointHolder; - -static_assert(Origin::x == 0); -static_assert(Origin::y == 0); -``` - -### 非类型模板参数的类模板参数推导(CTAD) - -```cpp -template -struct ValueWrapper { - constexpr static auto value = Value; -}; - -// CTAD -ValueWrapper w1{42}; // 推导为ValueWrapper -ValueWrapper w2{3.14}; // 推导为ValueWrapper -ValueWrapper w3{'a'}; // 推导为ValueWrapper -``` - -### Concepts约束非类型模板参数 - -```cpp -#include - -template -struct IntegralBuffer { - static constexpr auto size = N; - int data[N]; -}; - -template -struct FloatingWrapper { - static constexpr auto value = F; -}; - -IntegralBuffer<10> ib; // OK -// IntegralBuffer<3.14> ib2; // 错误:不是整数类型 - -FloatingWrapper<2.718> fw; // OK -// FloatingWrapper<42> fw2; // 错误:不是浮点类型 -``` - -### 三路比较支持 - -```cpp -template -requires requires { { Value } <=> std::declval(); } -struct OrderedValue { - constexpr static auto value = Value; - - constexpr auto operator<=>(const OrderedValue&) const = default; -}; - -OrderedValue<10> v1; -OrderedValue<20> v2; -static_assert(v1 < v2); -``` - ------- - -## 小结 - -非类型模板参数是C++模板系统中强大而独特的特性,特别适合嵌入式开发: - -### 关键要点总结 - -| 特性 | 说明 | 嵌入式应用 | -|------|------|----------| -| 整型参数 | 编译期常量整数 | 数组大小、位掩码、波特率 | -| 指针参数 | 编译期绑定的地址 | 寄存器映射、内存地址 | -| 引用参数 | 编译期绑定的引用 | 全局配置、设备描述 | -| auto参数(C++17) | 自动推导类型 | 通用值包装器 | -| 浮点参数(C++20) | 编译期浮点常量 | 三角函数表、物理常数 | - -### 实战建议 - -1. **类型安全优先** - - 使用强类型包装代替宏 - - 使用`static_assert`进行编译期验证 - - 使用标签类型防止类型混淆 - -2. **编译期计算** - - 将运行时计算移到编译期 - - 使用`constexpr`函数生成模板参数 - - 利用编译期常量进行优化 - -3. **代码组织** - - 将相关非类型模板参数组织成配置结构 - - 使用别名模板简化复杂类型 - - 为常用配置创建预定义类型 - -4. **性能考虑** - - 非类型模板参数不会增加运行时开销 - - 但过度使用会导致代码膨胀 - - 合理共享模板实例 - -### 检查清单 - -使用非类型模板参数时,确保: - -- [ ] 参数类型是编译期可确定的 -- [ ] 指针/引用参数指向外部链接对象 -- [ ] 数组大小在合理范围内 -- [ ] 添加适当的`static_assert`检查 -- [ ] 为常用配置提供类型别名 -- [ ] 文档说明参数的有效范围 - -**下一章**,我们将探讨**类模板**,学习如何实现泛型容器、理解模板成员函数的特殊规则,并深入探索模板特化与偏特化的高级应用。 diff --git a/documents/vol4-advanced/05-template-args-and-name-lookup.md b/documents/vol4-advanced/05-template-args-and-name-lookup.md deleted file mode 100644 index f498e2788..000000000 --- a/documents/vol4-advanced/05-template-args-and-name-lookup.md +++ /dev/null @@ -1,1467 +0,0 @@ ---- -title: "模板参数依赖与名字查找" -description: "深入理解C++模板的两阶段查找与依赖名称处理" -chapter: 12 -order: 5 -tags: - - cpp-modern - - host - - intermediate -difficulty: intermediate -reading_time_minutes: 40 -prerequisites: - - "Chapter 12: 模板入门概述" - - "Chapter 12: 函数模板详解" -cpp_standard: [11, 14, 17, 20] -platform: host ---- - -# 嵌入式现代C++教程——模板参数依赖与名字查找 - -你有没有遇到过这样的困惑:一段看起来完全正确的模板代码,编译器却报错说找不到某个类型?或者在模板内部调用某个函数时,明明这个函数存在,编译器却说它未定义? - -欢迎来到C++模板最令人头疼的部分——**名字查找**。理解两阶段查找、依赖名称、ADL这些概念,是成为C++模板高手的必经之路。 - ------- - -## 依赖名称(Dependent Names) - -### 什么是依赖名称? - -在模板代码中,**依赖名称**是指其含义依赖于模板参数的名称。当编译器解析模板时,它还不能确定这些名称代表什么——因为模板参数还未知。 - -```cpp -template -void process(T container) { - // value_type 是依赖名称——它的类型取决于 T - typename T::value_type* ptr; - - // clear 是依赖名称——它是否存在取决于 T - container.clear(); -} -``` - -**关键点**:编译器在解析模板定义时,还不知道`T`是什么类型,所以它无法确定`T::value_type`是一个类型、一个成员变量,还是一个静态函数。 - -### 依赖名称的分类 - -| 类别 | 说明 | 示例 | -|------|------|------| -| 依赖类型名称 | 依赖于模板参数的类型 | `T::value_type`、`T::iterator` | -| 依赖表达式名称 | 依赖于模板参数的表达式 | `t.get()`、`x + y` | -| 非依赖名称 | 不依赖模板参数的名称 | `int`、`std::vector` | - -```cpp -template -void example(T t) { - int x = 0; // 非依赖类型 - std::vector v; // 非依赖类型 - - typename T::type y; // 依赖类型 - t.method(); // 依赖表达式(method是否存在取决于T) - t + x; // 依赖表达式(operator+是否存在取决于T) -} -``` - -### typename 关键字的必要性 - -对于**依赖类型名称**,C++标准要求使用`typename`关键字明确告诉编译器"这是一个类型"。 - -```cpp -template -void func(T container) { - // ❌ 错误:编译器不知道 T::value_type 是类型还是成员 - T::value_type* ptr; - - // ✅ 正确:用 typename 明确指出这是一个类型 - typename T::value_type* ptr; -} -``` - -**为什么需要这个关键字?** - -因为C++语法本身有歧义。考虑下面两种情况: - -```cpp -// 情况1:value_type 是一个类型 -struct MyContainer { - using value_type = int; -}; - -// 情况2:value_type 是一个静态成员变量 -struct AnotherContainer { - static int value_type; -}; - -template -void ambiguous() { - T::value_type * p; - // 这是声明一个指向 T::value_type 类型的指针 p? - // 还是将 T::value_type(一个变量)与 p 相乘? -} -``` - -加上`typename`后,歧义就消除了: - -```cpp -template -void unambiguous() { - typename T::value_type* p; // 明确:声明指针 -} -``` - -### typename 的使用规则 - -**规则1**:只要在模板中使用`T::XXX`并且希望它是一个类型,就必须加`typename` - -```cpp -template -void process(T container) { - // 获取迭代器类型 - typename T::iterator it = container.begin(); - - // 获取值类型 - typename T::value_type val = *it; - - // 嵌套的情况 - typename T::template Inner::type x; -} -``` - -**规则2**:只有在模板参数依赖的上下文中才需要`typename` - -```cpp -template -class MyClass { - // 这里不需要 typename,因为不在模板函数体中 - using value_type = typename T::value_type; - - void method() { - // 这里需要 typename - typename T::some_type* ptr; - } -}; -``` - -**规则3**:某些上下文不需要`typename`(编译器知道必须是类型) - -```cpp -template -class Derived : public T::Base { // 基类列表不需要 typename - // ... -}; - -template -void func() { - // 类型转换不需要 typename - typedef typename T::type Type; - Type* p = static_cast(some_ptr); -} -``` - -### template 关键字的必要性 - -类似地,当访问依赖的成员模板时,需要使用`template`关键字: - -```cpp -template -void process(T container) { - // ❌ 错误:编译器不知道 < 是小于号还是模板参数列表开始 - auto ptr = container.get_ptr(); - - // ✅ 正确:用 template 明确指出这是成员模板 - auto ptr = container.template get_ptr(); -} -``` - -**完整的示例**: - -```cpp -template -struct Allocator { - template - struct rebind { - using other = Allocator; - }; -}; - -template -void example(Allocator alloc) { - // 获取 rebind::other - // 需要 typename(因为 rebind::other 是依赖类型) - // 需要 template(因为 rebind 是成员模板) - using ReboundAlloc = typename T::template rebind::other; -} -``` - -**何时使用 template 关键字**: - -| 情况 | 是否需要 | 示例 | -|------|----------|------| -| 访问成员类型 | 需要`typename` | `typename T::type` | -| 访问成员模板 | 需要`template` | `obj.template foo()` | -| 访问普通成员 | 不需要 | `obj.method()` | -| 访问静态成员 | 不需要 | `T::static_var` | - ------- - -## 两阶段查找(Two-Phase Lookup) - -### 什么是两阶段查找? - -C++编译器处理模板代码时,分为两个阶段进行名字查找: - -| 阶段 | 时机 | 查找内容 | 错误处理 | -|------|------|----------|----------| -| **阶段1** | 解析模板定义时 | 非依赖名称 | 立即报错 | -| **阶段2** | 实例化模板时 | 依赖名称 | 在实例化点报错 | - -```cpp -template -void func(T t) { - // 阶段1检查:非依赖名称 foo 必须可见 - foo(t); - - // 阶段2检查:依赖名称 t.bar() 在实例化时才检查 - t.bar(); -} - -void foo(int); // 必须在模板定义之前可见 - -// 使用 -func(42); // 阶段2:检查 int 是否有 bar() 方法 -``` - -### 阶段1:模板定义时 - -在这个阶段,编译器: - -1. 检查模板语法是否正确 -2. 查找所有**非依赖名称** -3. 验证`typename`和`template`关键字的使用 - -```cpp -// 正确示例 -template -void example1(T t) { - std::cout << t; // OK:std::cout 是非依赖名称,必须可见 -} - -// 错误示例 -template -void example2(T t) { - // 错误:print 未声明(即使 T 有 print 方法也不行) - // print 是非依赖名称,必须在定义时可见 - print(t); - - // 如果想调用 T 的方法,必须用 t.print() - t.print(); // OK:依赖名称,阶段2检查 -} -``` - -### 阶段2:模板实例化时 - -在这个阶段,编译器: - -1. 用具体的模板参数替换`T` -2. 查找所有**依赖名称** -3. 检查依赖的操作是否有效 - -```cpp -template -void process(T t) { - // 阶段1:operator<< 非依赖,必须有 std::operator<< 可见 - std::cout << t; - - // 阶段2:clear 是依赖名称,实例化时才检查 - t.clear(); -} - -struct A { void clear() {} }; -struct B { }; // 没有 clear 方法 - -process(A{}); // OK:A 有 clear 方法 -process(B{}); // 错误:B 没有 clear 方法(阶段2错误) -``` - -### 两阶段查找实战示例 - -```cpp -#include - -// 函数必须在模板定义之前定义 -void helper() { - std::cout << "Helper called\n"; -} - -template -void wrapper(T t) { - helper(); // 非依赖:必须在定义时可见 - t.method(); // 依赖:实例化时检查 -} - -// 如果把 helper 放在这里,编译失败! -// void helper() { } - -int main() { - wrapper(42); // 阶段2:检查 int 是否有 method() -} -``` - -**常见错误**: - -```cpp -// ❌ 错误示例 -template -void func(T t) { - using namespace std; // 在模板内部 using namespace - cout << t; // 依赖名称?非依赖名称?不明确! -} - -// ✅ 正确做法 -template -void func(T t) { - std::cout << t; // 明确的非依赖名称 -} - -// 或者 -template -void func(T t) { - using std::cout; // 明确引入 - cout << t; -} -``` - -### 两阶段查找与ADL的交互 - -```cpp -namespace MyNS { - struct A {}; - void foo(A); // ADL 候选 -} - -template -void call_foo(T t) { - foo(t); // 阶段1:检查 foo 是否存在 - // 阶段2:ADL 可能找到 MyNS::foo -} - -int main() { - MyNS::A a; - call_foo(a); // 通过 ADL 找到 MyNS::foo -} -``` - -**关键点**:依赖名称的查找会考虑ADL,而非依赖名称不会。 - -### 嵌入式开发中的注意事项 - -在嵌入式开发中,两阶段查找可能影响编译时间和错误诊断: - -```cpp -// 嵌入式场景:外设访问模板 -template -void init_peripheral() { - // 阶段1检查:必须能看到这些 - using namespace MCU::Registers; - - // 阶段2检查:Periph 必须有这些成员 - Periph::enable_clock(); - Periph::reset(); -} - -// 使用 -struct UART1 { - static void enable_clock(); - static void reset(); -}; - -init_peripheral(); // 阶段2验证 -``` - ------- - -## ADL(Argument-Dependent Lookup)详解 - -### 什么是ADL? - -**参数依赖查找**(Argument-Dependent Lookup,ADL),也叫**Koenig查找**,是一种特殊的名字查找规则。它允许编译器在参数类型的命名空间中查找函数。 - -```cpp -namespace MyNS { - struct A {}; - void do_something(A); // 定义在 MyNS 中 -} - -int main() { - MyNS::A a; - do_something(a); // 无需前缀!ADL 自动找到 MyNS::do_something -} -``` - -### ADL的基本规则 - -**规则1**:函数调用时,编译器会在以下位置查找: - -1. 当前作用域 -2. 外层作用域(普通查找) -3. 参数类型的命名空间(ADL) - -```cpp -namespace NS1 { - struct X {}; - void func(X); // NS1::func -} - -namespace NS2 { - struct Y {}; - void func(Y); // NS2::func -} - -void test() { - NS1::X x; - NS2::Y y; - func(x); // ADL 找到 NS1::func - func(y); // ADL 找到 NS2::func -} -``` - -**规则2**:ADL 只对函数有效,对类模板无效 - -```cpp -namespace MyNS { - struct A {}; - template void func(T); - template class MyClass; // 类模板不支持 ADL -} - -int main() { - MyNS::A a; - func(a); // OK:ADL 工作于函数模板 - - // MyClass obj; // 错误:必须写 MyNS::MyClass -} -``` - -**规则3**:ADL 忽略 using 指令 - -```cpp -namespace Lib { - struct X {}; - void lib_func(X); -} - -namespace App { - using namespace Lib; // using 指令 - - void test() { - X x; - lib_func(x); // ADL 找到 Lib::lib_func - } -} -``` - -### ADL与运算符重载 - -ADL最重要的应用是运算符重载: - -```cpp -namespace Math { - struct Vector { - double x, y; - }; - - Vector operator+(Vector a, Vector b) { - return {a.x + b.x, a.y + b.y}; - } -} - -int main() { - using Math::Vector; - - Vector v1{1, 2}; - Vector v2{3, 4}; - - // v1 + v2 实际上调用 operator+(v1, v2) - // 通过 ADL 找到 Math::operator+ - Vector v3 = v1 + v2; // 无需 Math::operator+! -} -``` - -**这就是为什么我们可以直接写`a + b`而不需要写`operator+(a, b)`或`SomeNS::operator+(a, b)`。** - -### ADL在模板中的特殊规则 - -在模板中,ADL规则变得更加复杂: - -```cpp -namespace NS { - struct A {}; - void foo(A); -} - -template -void call_foo(T t) { - foo(t); // 阶段1:foo 必须可见(至少声明) - // 阶段2:通过 ADL 查找 -} - -// 在模板定义点,foo 必须至少被声明 -void foo(...); // 前向声明即可 - -int main() { - NS::A a; - call_foo(a); // 通过 ADL 找到 NS::foo -} -``` - -### ADL与友元函数 - -```cpp -class MyClass { - friend void friend_func(MyClass); // 友元声明 - -private: - int secret = 42; -}; - -// 定义(可以是外部的) -void friend_func(MyClass obj) { - std::cout << obj.secret; // 可以访问私有成员 -} - -int main() { - MyClass obj; - friend_func(obj); // ADL 找到友元函数 -} -``` - -### ADL陷阱与注意事项 - -**陷阱1:名字隐藏** - -```cpp -namespace Lib { - struct X {}; - void process(X); -} - -namespace App { - void process(void*); // 不同签名 - - void test() { - Lib::X x; - process(x); // 错误:只找到 App::process,不匹配 Lib::X - // ADL 找到的 Lib::process 被隐藏 - } -} -``` - -**解决方案**:显式使用命名空间 - -```cpp -void test() { - Lib::X x; - Lib::process(x); // 明确调用 -} -``` - -**陷阱2:无限递归** - -```cpp -namespace NS { - struct X; - void to_string(X); - - struct X { - // 错误:无限递归 - std::string str() { - return to_string(*this); // 调用自己 - } - }; -} -``` - -**陷阱3:std 命名空间的特殊性** - -标准库的某些函数不参与ADL: - -```cpp -namespace std { - struct string {}; - void some_func(string); // 假设的函数 -} - -int main() { - std::string s; - some_func(s); // 不一定通过 ADL 找到 - // 添加到 std 是未定义行为! -} -``` - -**重要**:永远不要向`std`命名空间添加内容! - -### ADL实战:自定义迭代器 - -```cpp -namespace MyContainer { - template - class Container { - public: - class iterator { - T* ptr; - public: - iterator(T* p) : ptr(p) {} - iterator& operator++() { ++ptr; return *this; } - T& operator*() { return *ptr; } - }; - - iterator begin() { return iterator(data_); } - iterator end() { return iterator(data_ + size_); } - - private: - T data_[100]; - std::size_t size_ = 0; - }; - - // 自定义 begin/end 以支持 ADL - template - typename Container::iterator begin(Container& c) { - return c.begin(); - } - - template - typename Container::iterator end(Container& c) { - return c.end(); - } -} - -int main() { - using namespace MyContainer; - Container c; - - // 通过 ADL 找到 begin/end - for (auto& x : c) { - // ... - } -} -``` - -### ADL总结表 - -| 场景 | ADL是否工作 | 示例 | -|------|-------------|------| -| 普通函数 | 是 | `func(obj)` 找到 `NS::func` | -| 运算符 | 是 | `a + b` 找到 `NS::operator+` | -| 类模板 | 否 | `MyClass` 需要完整路径 | -| 成员函数 | N/A | `obj.method()` 不涉及ADL | -| 命名空间别名 | 是 | 通过别名类型仍能触发 | - ------- - -## 实战:正确编写泛型迭代器代码 - -让我们综合运用所学知识,编写一个正确、健壮的泛型迭代器。 - -### 需求分析 - -我们的迭代器需要: - -1. 支持任意容器类型 -2. 正确处理依赖类型名称 -3. 支持ADL以便于使用 -4. 提供类型安全的访问 - -### 基础实现 - -```cpp -template -class GenericIterator { -public: - // 依赖类型:必须使用 typename - using value_type = typename Container::value_type; - using reference = typename Container::reference; - using pointer = typename Container::pointer; - using difference_type = typename Container::difference_type; - -private: - Container& container_; - std::size_t index_; - -public: - explicit GenericIterator(Container& c, std::size_t index = 0) - : container_(c), index_(index) {} - - // 解引用 - reference operator*() { - return container_[index_]; - } - - // 箭头运算符 - pointer operator->() { - return &container_[index_]; - } - - // 前置递增 - GenericIterator& operator++() { - ++index_; - return *this; - } - - // 后置递增 - GenericIterator operator++(int) { - auto temp = *this; - ++index_; - return temp; - } - - // 比较 - bool operator==(const GenericIterator& other) const { - return index_ == other.index_; - } - - bool operator!=(const GenericIterator& other) const { - return !(*this == other); - } -}; -``` - -### 增强版:支持没有标准 typedef 的容器 - -某些容器可能不提供`value_type`等标准typedef,我们需要使用SFINAE: - -```cpp -// 类型萃取辅助 -template -struct container_traits { - // 默认情况下,假设有标准 typedef - using value_type = typename T::value_type; -}; - -// 针对 C 数组的特化 -template -struct container_traits { - using value_type = T; - using reference = T&; - using pointer = T*; -}; - -template -class AdvancedIterator { - // 使用萃取获取类型,提供合理的默认值 - using traits = container_traits; - using value_type = typename traits::value_type; - -private: - Container& container_; - std::size_t index_; - -public: - explicit AdvancedIterator(Container& c, std::size_t index = 0) - : container_(c), index_(index) {} - - // 类型安全的访问 - value_type& operator*() { - return container_[index_]; - } - - value_type* operator->() { - return &container_[index_]; - } - - // ... 其他运算符 -}; -``` - -### 完整的泛型访问函数 - -```cpp -// 获取容器大小(支持数组和容器) -template -constexpr std::size_t size(T (&)[N]) noexcept { - return N; -} - -template -constexpr auto size(const Container& c) noexcept -> decltype(c.size()) { - return c.size(); -} - -// 获取迭代器 -template -auto begin(Container& c) -> decltype(c.begin()) { - return c.begin(); -} - -template -T* begin(T (&arr)[N]) { - return arr; -} - -template -auto end(Container& c) -> decltype(c.end()) { - return c.end(); -} - -template -T* end(T (&arr)[N]) { - return arr + N; -} -``` - -### 嵌入式应用:通用的寄存器访问迭代器 - -```cpp -namespace MCU { - // 寄存器访问基类 - template - class RegisterIterator { - volatile RegType* base_; - std::size_t offset_; - - public: - using value_type = RegType; - using reference = RegType&; - using pointer = volatile RegType*; - - RegisterIterator(volatile RegType* base, std::size_t offset) - : base_(base), offset_(offset) {} - - reference operator*() const { - return const_cast(base_[offset_]); - } - - pointer operator->() const { - return base_ + offset_; - } - - RegisterIterator& operator++() { - ++offset_; - return *this; - } - - RegisterIterator operator++(int) { - auto temp = *this; - ++offset_; - return temp; - } - - bool operator==(const RegisterIterator& other) const { - return offset_ == other.offset_; - } - - bool operator!=(const RegisterIterator& other) const { - return !(*this == other); - } - }; - - // GPIO 端口访问 - template - class GPIOPort { - public: - using iterator = RegisterIterator; - - static constexpr std::size_t register_count = 16; - - static volatile std::uint32_t* base() { - return reinterpret_cast(PortBase); - } - - iterator begin() { - return iterator(base(), 0); - } - - iterator end() { - return iterator(base(), register_count); - } - }; -} - -// 使用示例 -void clear_all_gpio() { - constexpr std::size_t GPIOA_BASE = 0x40020000; - MCU::GPIOPort port; - - for (auto& reg : port) { - reg = 0; // 清零所有寄存器 - } -} -``` - -### 带约束的迭代器(C++20 Concepts) - -```cpp -template -concept Iterable = requires(T t) { - typename T::value_type; - { t.begin() } -> std::same_as; - { t.end() } -> std::same_as; -}; - -template -class SafeIterator { - using value_type = typename Container::value_type; - // ... -}; - -// 使用 -SafeIterator> it1; // OK -// SafeIterator it2; // 编译错误 -``` - -### 完整示例:泛型打印函数 - -```cpp -#include -#include -#include - -// 检查是否有 value_type -template -struct has_value_type : std::false_type {}; - -template -struct has_value_type> - : std::true_type {}; - -// 泛型打印函数 -template -auto print_container(const Container& c) - -> std::enable_if_t::value, void> -{ - std::cout << "["; - bool first = true; - for (const auto& item : c) { - if (!first) std::cout << ", "; - std::cout << item; - first = false; - } - std::cout << "]\n"; -} - -// C数组特化 -template -void print_container(const T (&arr)[N]) { - std::cout << "["; - for (std::size_t i = 0; i < N; ++i) { - if (i > 0) std::cout << ", "; - std::cout << arr[i]; - } - std::cout << "]\n"; -} - -// 使用 -int main() { - std::vector vec = {1, 2, 3, 4, 5}; - std::array arr = {1.1, 2.2, 3.3}; - int c_arr[] = {10, 20, 30}; - - print_container(vec); // [1, 2, 3, 4, 5] - print_container(arr); // [1.1, 2.2, 3.3] - print_container(c_arr); // [10, 20, 30] -} -``` - -::: details 完整可运行示例:泛型迭代器工具包 - -```cpp -#include -#include -#include -#include - -namespace generic { - -// ============ 类型萃取 ============ -template -struct container_traits { - using value_type = typename T::value_type; - using size_type = typename T::size_type; - using reference = typename T::reference; - using const_reference = typename T::const_reference; -}; - -// C数组特化 -template -struct container_traits { - using value_type = T; - using size_type = std::size_t; - using reference = T&; - using const_reference = const T&; -}; - -// ============ 容器访问函数 ============ - -// size 函数 -template -constexpr std::size_t size(T (&)[N]) noexcept { - return N; -} - -template -constexpr auto size(const Container& c) noexcept -> decltype(c.size()) { - return c.size(); -} - -// begin 函数(ADL 友好) -template -auto begin(Container& c) -> decltype(c.begin()) { - return c.begin(); -} - -template -auto begin(const Container& c) -> decltype(c.begin()) { - return c.begin(); -} - -template -T* begin(T (&arr)[N]) { - return arr; -} - -template -const T* begin(const T (&arr)[N]) { - return arr; -} - -// end 函数(ADL 友好) -template -auto end(Container& c) -> decltype(c.end()) { - return c.end(); -} - -template -auto end(const Container& c) -> decltype(c.end()) { - return c.end(); -} - -template -T* end(T (&arr)[N]) { - return arr + N; -} - -template -const T* end(const T (&arr)[N]) { - return arr + N; -} - -// ============ 泛型遍历 ============ - -template -void for_each(Container&& c, Func&& func) { - using std::begin; - using std::end; - auto first = begin(c); - auto last = end(c); - for (; first != last; ++first) { - func(*first); - } -} - -// ============ 累加器 ============ - -template -auto sum(const Container& c) -> typename container_traits::value_type { - using value_type = typename container_traits::value_type; - value_type result{}; - for_each(c, [&result](const value_type& v) { result += v; }); - return result; -} - -// ============ 打印器 ============ - -namespace detail { - struct print_fn { - template - void operator()(const T& v) const { - std::cout << v; - } - }; -} - -template -void print(const Container& c) { - std::cout << "["; - bool first = true; - for_each(c, [&](const auto& v) { - if (!first) std::cout << ", "; - detail::print_fn{}(v); - first = false; - }); - std::cout << "]"; -} - -} // namespace generic - -// ============ 使用示例 ============ -int main() { - using namespace generic; - - std::vector vec = {1, 2, 3, 4, 5}; - std::array arr = {1.1, 2.2, 3.3}; - int c_arr[] = {10, 20, 30}; - - std::cout << "Vector: "; - print(vec); - std::cout << ", size=" << size(vec) << ", sum=" << sum(vec) << "\n"; - - std::cout << "Array: "; - print(arr); - std::cout << ", size=" << size(arr) << ", sum=" << sum(arr) << "\n"; - - std::cout << "C-array: "; - print(c_arr); - std::cout << ", size=" << size(c_arr) << ", sum=" << sum(c_arr) << "\n"; - - // 使用 for_each - std::cout << "Squared: "; - for_each(vec, [](int x) { std::cout << x * x << " "; }); - std::cout << "\n"; - - return 0; -} - -``` - -::: - ------- - -## 常见陷阱:为什么 `t.clear()` 有时不工作? - -### 陷阱1:依赖名称与两阶段查找 - -```cpp - -template -void process(T t) { - // 阶段1:编译器检查 clear 是否作为非依赖名称存在 - // 阶段2:编译器检查 T 是否有 clear 方法 - - t.clear(); // 如果 T 没有 clear(),阶段2报错 -} - -struct HasClear { - void clear() { std::cout << "Cleared!\n"; } -}; - -struct NoClear {}; // 没有 clear 方法 - -int main() { - process(HasClear{}); // OK - process(NoClear{}); // 错误:NoClear 没有 clear 成员 -} -``` - -**解决方案**:使用SFINAE或Concepts约束 - -```cpp -// C++17 SFINAE -template -auto process_safe(T t) -> decltype(t.clear(), void()) { - t.clear(); -} - -// C++20 Concepts -template -concept Clearable = requires(T t) { t.clear(); }; - -template -void process_concept(T t) { - t.clear(); -} -``` - -### 陷阱2:ADL与名字隐藏 - -```cpp -namespace Lib { - struct Data {}; - void process(Data d) { std::cout << "Lib::process\n"; } -} - -namespace App { - void process(void*) { std::cout << "App::process\n"; } - - void test() { - Lib::Data d; - process(d); // 错误!只找到 App::process - // Lib::process 被"隐藏"了 - } -} -``` - -**为什么会这样?** - -因为普通名字查找在当前作用域找到了`App::process`,即使它不匹配,ADL也不会继续查找。 - -**解决方案1**:显式调用命名空间 - -```cpp -void test() { - Lib::Data d; - Lib::process(d); // 明确指定 -} -``` - -**解决方案2**:引入到当前作用域 - -```cpp -namespace App { - using Lib::process; // 引入 - void process(void*) { std::cout << "App::process\n"; } - - void test() { - Lib::Data d; - process(d); // 现在可以找到 Lib::process - } -} -``` - -### 陷阱3:typename 位置错误 - -```cpp -template -struct MyClass { - // 错误:这里不需要 typename(不在函数体中) - using type = typename T::value_type; - - void method() { - // 正确:这里需要 typename - typename T::some_type* ptr; - } -}; - -// 更复杂的错误 -template -void func() { - // 错误:嵌套的成员模板需要 template 关键字 - // T::template Inner::type* ptr; - - // 正确写法 - typename T::template Inner::type* ptr; -} -``` - -### 陷阱4:operator+ 与 ADL - -```cpp -namespace MyMath { - struct Vector { double x, y; }; - Vector operator+(Vector a, Vector b) { - return {a.x + b.x, a.y + b.y}; - } -} - -template -void add_and_print(T a, T b) { - // 这里依赖 ADL 找到 operator+ - auto c = a + b; // OK:通过 ADL 找到 MyMath::operator+ - std::cout << c.x << ", " << c.y << "\n"; -} - -int main() { - MyMath::Vector v1{1, 2}; - MyMath::Vector v2{3, 4}; - add_and_print(v1, v2); // 正常工作 -} -``` - -但如果这样写: - -```cpp -template -void broken_add(T a, T b) { - using std::operator+; // 错误做法! - auto c = a + b; // 可能找不到 MyMath::operator+ -} -``` - -### 陷阱5:友元函数与ADL - -```cpp -class SecretHolder { - int secret = 42; - friend void reveal(const SecretHolder&); // 友元声明 -}; - -// 必须在命名空间作用域定义 -void reveal(const SecretHolder& s) { - std::cout << s.secret << "\n"; // OK:友元可以访问 -} - -int main() { - SecretHolder s; - reveal(s); // 通过 ADL 找到友元函数 -} -``` - -但如果把定义放在类内: - -```cpp -class SecretHolder { - int secret = 42; - friend void reveal(const SecretHolder& s) { - std::cout << s.secret << "\n"; // 不是成员函数! - } -}; - -int main() { - SecretHolder s; - reveal(s); // 错误!类内定义的友元不参与普通名字查找 -} -``` - -**解决方案**:类内定义的友元只能通过ADL找到,但类内定义不会在调用点可见。需要前向声明或外部定义。 - -### 陷阱6:模板基类的成员不可见 - -```cpp -template -struct Base { - void method() { std::cout << "Base::method\n"; } - using value_type = T; -}; - -template -struct Derived : Base { - void use_method() { - // method(); // 错误!编译器不查找依赖基类 - - // 解决方案1:使用 this-> 令其成为依赖名称 - this->method(); - - // 解决方案2:使用 using 声明 - using Base::method; - method(); - - // 解决方案3:完全限定名 - Base::method(); - } - - void use_type() { - // value_type x; // 错误! - - typename Base::value_type x; // 正确 - } -}; -``` - -**解释**:因为`Base`是依赖基类(依赖模板参数),编译器在阶段1不会查找它的成员。必须使用`this->`或完全限定名令其成为依赖名称。 - -### 陷阱对照表 - -| 陷阱 | 原因 | 解决方案 | -|------|------|----------| -| `t.clear()` 不工作 | `T` 没有 `clear` 方法 | SFINAE/Concepts 约束 | -| 找不到同名函数 | 名字隐藏 | 显式命名空间或 `using` | -| `typename` 位置错误 | 非函数体的类型不需要 | 只在依赖类型处使用 | -| 成员模板访问 | 缺少 `template` 关键字 | 使用 `obj.template foo()` | -| 基类成员不可见 | 依赖基类查找规则 | 使用 `this->` 或 `using` | -| 友元函数不可见 | ADL 规则限制 | 确保在命名空间作用域定义 | - -### 调试模板名字查找问题 - -当遇到名字查找问题时,按以下步骤排查: - -1. **检查是否是依赖名称**:名称是否依赖模板参数? -2. **检查是否需要 `typename`**:是否是依赖类型? -3. **检查两阶段查找**:非依赖名称在定义时是否可见? -4. **检查ADL**:函数是否在参数类型的命名空间中? -5. **检查基类**:是否是依赖基类的成员? -6. **检查约束**:是否需要 SFINAE 或 Concepts? - -```cpp -// 调试技巧:static_assert 提供清晰错误 -template -void debug_process(T t) { - using value_type = typename T::value_type; // 如果失败,给出明确错误 - static_assert(std::is_same_v, "Only int containers supported"); - // ... -} -``` - ------- - -## 小结 - -模板参数依赖与名字查找是C++模板的核心机制,理解它们对于编写正确的模板代码至关重要。 - -### 关键概念回顾 - -| 概念 | 核心要点 | 使用场景 | -|------|----------|----------| -| **依赖名称** | 依赖模板参数的名称 | 在模板中访问`T::XXX` | -| **typename** | 标明依赖类型 | `typename T::value_type` | -| **template** | 标明依赖成员模板 | `obj.template foo()` | -| **两阶段查找** | 定义时查非依赖,实例化时查依赖 | 理解编译错误时机 | -| **ADL** | 在参数类型命名空间查找函数 | 运算符重载、swap 等 | - -### 实战建议 - -1. **始终对依赖类型使用 `typename`** - - ```cpp - typename T::value_type* ptr; - ``` - -2. **对依赖成员模板使用 `template` 关键字** - - ```cpp - obj.template method(); - ``` - -3. **理解两阶段查找,合理安排代码** - - ```cpp - // 非依赖名称:在模板定义前声明 - void helper(); - - template - void func(T t) { - helper(); // 非依赖:定义时检查 - t.method(); // 依赖:实例化时检查 - } - ``` - -4. **利用 ADL 简化函数调用** - - ```cpp - // 用户可以写 swap(a, b) 而非 std::swap(a, b) - using std::swap; - swap(a, b); - ``` - -5. **使用 Concepts 提供清晰约束** - - ```cpp - template - concept Clearable = requires(T t) { t.clear(); }; - - template - void process(T t) { t.clear(); } - ``` - -6. **注意依赖基类成员的特殊规则** - - ```cpp - template - struct Derived : Base { - void foo() { - this->method(); // 使用 this-> 令其成为依赖名称 - } - }; - ``` - -### C++标准演进 - -| 标准 | 新特性 | 简化了什么 | -|------|--------|-----------| -| C++11 | `decltype` | 更精确的类型推导 | -| C++14 | `decltype(auto)` | 自动推导引用类型 | -| C++17 | `std::void_t` | 更简单的 SFINAE | -| C++17 | `if constexpr` | 编译期分支 | -| C++20 | Concepts | 清晰的模板约束 | -| C++20 | `requires` | 更好的约束表达 | - -**下一章**,我们将探讨**可变参数模板**,学习如何编写接受任意数量参数的模板函数,并实现一个类型安全的嵌入式事件系统。 diff --git a/documents/vol4-advanced/06-template-friends-and-barton-nackman.md b/documents/vol4-advanced/06-template-friends-and-barton-nackman.md deleted file mode 100644 index 0bcc9a928..000000000 --- a/documents/vol4-advanced/06-template-friends-and-barton-nackman.md +++ /dev/null @@ -1,1352 +0,0 @@ ---- -title: "模板友元与 Barton-Nackman 技巧" -description: "掌握友元注入机制与运算符重载的模板技巧" -chapter: 12 -order: 6 -tags: - - cpp-modern - - host - - intermediate -difficulty: intermediate -reading_time_minutes: 35 -prerequisites: - - "Chapter 12: 类模板详解" - - "Chapter 12: 模板实例化与特化" -cpp_standard: [11, 14, 17, 20] -platform: host ---- - -# 嵌入式现代C++教程——模板友元与 Barton-Nackman 技巧 - -你有没有想过,为什么标准库的 `std::complex` 或 `std::pair` 可以直接用 `==` 比较?为什么它们不需要在全局作用域定义一堆运算符函数?答案就是——**友元注入**与 **Barton-Nackman Trick**。 - -这是一种优雅的模板技术,它不仅让运算符重载变得简洁,更是 CRTP(奇异递归模板模式)的前身。本章将深入探讨这一机制的原理,并实现一个功能完整的可比较 `Point` 类型。 - ------- - -## 友元函数与模板的基本关系 - -在理解 Barton-Nackman Trick 之前,我们需要先回顾 C++ 中友元函数的基本概念,以及它与模板的结合方式。 - -### 普通类的友元函数 - -对于非模板类,定义友元运算符非常简单: - -```cpp -class Point { - double x, y; -public: - Point(double x, double y) : x(x), y(y) {} - - // 友元函数声明 - friend bool operator==(const Point& a, const Point& b) { - return a.x == b.x && a.y == b.y; - } - - friend bool operator!=(const Point& a, const Point& b) { - return !(a == b); - } -}; - -// 使用 -Point p1{1, 2}, p2{3, 4}; -bool eq = (p1 == p2); // 正常工作 -``` - -这种方式下,友元函数在类内部定义,但属于外部作用域(可以在类外部调用)。 - -### 类模板的友元函数困境 - -当我们把 `Point` 改成模板时,问题就出现了: - -```cpp -template -class Point { - T x, y; -public: - Point(T x, T y) : x(x), y(y) {} - - // 尝试定义友元运算符 - friend bool operator==(const Point& a, const Point& b) { - return a.x == b.x && a.y == b.y; - } -}; - -// 使用 -Point p1{1, 2}; -Point p2{3, 4}; -// bool eq = (p1 == p2); // ❌ 链接错误!未定义的引用 -``` - -为什么?因为模板类的友元函数本身**不是模板**,而是针对每个实例化类型生成的**非模板函数**。如果友元函数定义在类内部,它是内联的,理论上应该能工作。但实践中,某些编译器可能会在链接时出现问题。 - -更重要的是,如果我们想让 `Point` 和 `Point` 也能比较,这种方式就无能为力了。 - ------- - -## 友元注入机制 - -现在让我们介绍核心概念——**友元注入**(Friend Injection)。 - -### 什么是友元注入? - -友元注入是指:当在类模板内部定义一个友元函数时,这个函数不仅成为类的友元,还会被**注入到外围作用域**(通常是全局或命名空间作用域),并且**可以通过参数依赖查找(ADL)找到**。 - -关键点:这个友元函数**不是模板函数**,而是一个**非模板函数**,但它可以访问类的私有成员。 - -### 基本语法 - -```cpp -template -class Point { - T x, y; -public: - Point(T x, T y) : x(x), y(y) {} - - // 友元注入:函数在类内定义,但可在外部通过ADL找到 - friend bool operator==(const Point& a, const Point& b) { - return a.x == b.x && a.y == b.y; - } -}; - -// 使用 -Point p1{1, 2}; -Point p2{1, 2}; - -// 通过ADL找到operator== -bool eq = (p1 == p2); // ✅ 正常工作 -// 等价于 bool eq = operator==(p1, p2); -// 但 operator== 不在全局作用域,只能通过ADL找到 -``` - -### ADL(参数依赖查找)的关键作用 - -ADL 是 C++ 名称查找规则的一部分:当调用一个函数时,编译器不仅会在当前作用域查找,还会在**参数类型所在的命名空间**查找。 - -```cpp -namespace geometry { - template - class Point { - T x, y; - public: - Point(T x, T y) : x(x), y(y) {} - - // 友元函数 - friend bool operator==(const Point& a, const Point& b) { - return a.x == b.x && a.y == b.y; - } - }; -} - -// 使用 -geometry::Point p1{1, 2}, p2{1, 2}; - -// ❌ 如果写 operator==(p1, p2) 会找不到 -// ✅ 但写 p1 == p2 可以通过ADL找到 -bool eq = (p1 == p2); // ADL在geometry命名空间中查找operator== -``` - -### 友元注入的三个关键特性 - -| 特性 | 说明 | 示例 | -|------|------|------| -| 非模板函数 | 每次实例化生成独立的非模板函数 | `Point` 生成一个 `operator==`,`Point` 生成另一个 | -| 内联定义 | 函数体必须在类内部定义 | 不能在类内声明、类外定义 | -| ADL可查找 | 只能通过参数依赖查找找到 | 直接写 `operator==` 可能找不到 | - -```cpp -template -class Point { - // ... - friend bool operator==(const Point& a, const Point& b) { - return a.x == b.x && a.y == b.y; - } -}; - -Point p1, p2; -p1 == p2; // ✅ ADL找到 -// operator==(p1, p2); // ❌ 可能找不到(取决于编译器) -``` - ------- - -## Barton-Nackman Trick - -现在我们进入本章的核心——Barton-Nackman Trick(也称为 "受限的模板友元注入")。 - -### 历史背景 - -这个技巧由 John Barton 和 Lee Nackman 在 1994 年的著作 *Scientific and Engineering C++* 中首次描述。它是最早的约束泛型编程技术之一,是后来 CRTP(奇异递归模板模式)和 C++20 Concepts 的思想源头。 - -### 核心思想 - -**在类模板内部定义一个友元函数模板,该函数模板的参数类型受类模板参数约束。** - -```cpp -template -class Point { - T x, y; -public: - Point(T x, T y) : x(x), y(y) {} - - // Barton-Nackman Trick - // 友元函数模板,约束为只能比较相同类型的Point - friend bool operator==(const Point& a, const Point& b) { - return a.x == b.x && a.y == b.y; - } -}; -``` - -等等,这和之前的友元注入有什么区别?关键在于:**这里的 `operator==` 是一个函数模板**,而非模板函数。 - -### 正确的 Barton-Nackman 语法 - -为了真正定义友元函数模板,我们需要显式声明模板参数: - -```cpp -template -class Point { - T x, y; -public: - Point(T x, T y) : x(x), y(y) {} - - // 方式1:非模板友元(之前讲过的) - friend bool operator==(const Point& a, const Point& b) { - return a.x == b.x && a.y == b.y; - } - - // 方式2:真正的 Barton-Nackman - 友元函数模板 - template - friend bool operator==(const Point& a, const Point& b) { - return a.x == b.x && a.y == b.y; - } -}; -``` - -但实际上,方式1(非模板友元)在大多数场景下已经足够,并且是现代C++推荐的方式。方式2(真正的函数模板)只有在需要跨类型比较时才需要。 - -### Barton-Nackman 的约束作用 - -传统 Barton-Nackman Trick 的真正威力在于**约束**:通过将运算符定义为类的友元,只有当操作数类型匹配类模板时,该运算符才参与重载决议。 - -```cpp -template -class Point { - T x, y; -public: - // 这个operator==只对Point及其派生类可见 - friend bool operator==(const Point& a, const Point& b) { - return a.x == b.x && a.y == b.y; - } -}; - -// 全局的一个通用operator== -template -bool operator==(const T& a, const U& b) { - return false; -} - -Point p{1, 2}; -int x = 5; - -p == p; // 调用Point的友元operator== -x == p; // 调用通用operator==(Point的友元不匹配) -``` - -### 简化的现代写法 - -在现代C++(C++11及以后)中,Barton-Nackman Trick 的核心价值已经减弱,因为我们有更好的技术(如 `std::enable_if`、C++20 Concepts)。但友元注入的语法仍然简洁实用: - -```cpp -template -class Point { - T x, y; -public: - Point(T x, T y) : x(x), y(y) {} - - // 现代推荐写法:简洁的友元注入 - friend bool operator==(const Point& a, const Point& b) { - return a.x == b.x && a.y == b.y; - } - - friend bool operator!=(const Point& a, const Point& b) { - return !(a == b); - } - - // 其他比较运算符... - friend auto operator<=>(const Point& a, const Point& b) { - if (auto cmp = a.x <=> b.x; cmp != 0) return cmp; - return a.y <=> b.y; - } -}; -``` - -**注意**:C++20 的三路比较运算符 `operator<=>` 会自动生成所有其他比较运算符,所以只需要定义它就够了。 - ------- - -## Barton-Nackman 与 CRTP 的关系 - -Barton-Nackman Trick 是 CRTP 的前身。理解两者的关系有助于深入掌握模板元编程。 - -### CRTP:奇异递归模板模式 - -CRTP 是一种设计模式,派生类将自身作为基类的模板参数: - -```cpp -template -class Base { -public: - void interface() { - // 编译期将基类指针转换为派生类指针 - static_cast(this)->implementation(); - } -}; - -class Derived : public Base { -public: - void implementation() { - // 具体实现 - } -}; -``` - -### Barton-Nackman 到 CRTP 的演变 - -早期的 Barton-Nackman Trick 代码看起来像这样: - -```cpp -// Barton-Nackman 原始风格(简化版) -template -class Ordered { -public: - friend bool operator<(const T& a, const T& b) { - return a.less(b); - } - friend bool operator>(const T& a, const T& b) { - return b < a; - } - // ...其他运算符 -}; - -class Point : public Ordered { - double x, y; -public: - bool less(const Point& other) const { - if (x != other.x) return x < other.x; - return y < other.y; - } -}; -``` - -注意这里 `Point` 继承自 `Ordered`——这就是 CRTP 的核心! - -### 现代实现(使用 CRTP) - -```cpp -template -class Comparable { -public: - friend bool operator<(const Derived& a, const Derived& b) { - return a.compare(b) < 0; - } - - friend bool operator>(const Derived& a, const Derived& b) { - return b < a; - } - - friend bool operator<=(const Derived& a, const Derived& b) { - return !(a > b); - } - - friend bool operator>=(const Derived& a, const Derived& b) { - return !(a < b); - } - - friend bool operator==(const Derived& a, const Derived& b) { - return a.compare(b) == 0; - } - - friend bool operator!=(const Derived& a, const Derived& b) { - return !(a == b); - } -}; - -template -class Point : public Comparable> { - T x, y; -public: - Point(T x, T y) : x(x), y(y) {} - - int compare(const Point& other) const { - if (x < other.x) return -1; - if (x > other.x) return 1; - if (y < other.y) return -1; - if (y > other.y) return 1; - return 0; - } -}; -``` - -### 两种模式对比 - -| 特性 | Barton-Nackman Trick | CRTP | -|------|---------------------|------| -| 年代 | 1994年 | 1990年代末 | -| 核心 | 友元注入 | 继承+模板 | -| 运算符位置 | 在类内定义友元函数 | 在基类定义友元函数 | -| 代码复用 | 每个类重复定义 | 基类统一实现 | -| 灵活性 | 较低 | 较高 | -| 现代适用性 | 简单场景够用 | 复杂层次结构推荐 | - -**选择建议**: - -- **简单类**:直接使用友元注入,不需要 CRTP -- **需要共享大量运算符逻辑**:使用 CRTP 基类 -- **C++20**:考虑使用 Concepts 约束的运算符 - ------- - -## 运算符重载的模板技巧 - -让我们探讨几种常见的运算符重载模板技巧。 - -### 技巧1:友元函数 vs 成员函数 - -```cpp -template -class Point { - T x, y; -public: - - // ❌ 成员函数:不对称,需要 Point == 其他类型 能工作 - bool operator==(const Point& other) const { - return x == other.x && y == other.y; - } - - // ✅ 友元函数:对称,两边都能处理隐式转换 - friend bool operator==(const Point& a, const Point& b) { - return a.x == b.x && a.y == b.y; - } -}; -``` - -**最佳实践**: - -- **赋值、下标、调用、箭头**:必须是成员函数 -- **复合赋值(+=、-=等)**:通常是成员函数 -- **算术、比较、IO**:通常是非成员(友元)函数 -- **类型转换**:必须是成员函数 - -### 技巧2:跨类型比较 - -使用模板友元实现不同类型之间的比较: - -```cpp -template -class Point { - T x, y; -public: - Point(T x, T y) : x(x), y(y) {} - - // 同类型比较 - friend bool operator==(const Point& a, const Point& b) { - return a.x == b.x && a.y == b.y; - } - - // 跨类型比较(int 和 double 可以比较) - template - friend bool operator==(const Point& a, const Point& b) { - return a.x == b.x && a.y == b.y; - } -}; - -// 使用 -Point pi{1, 2}; -Point pd{1.0, 2.0}; -bool eq = (pi == pd); // ✅ 跨类型比较 -``` - -### 技巧3:使用 std::common_type 统一返回类型 - -```cpp -#include - -template -auto add(const T& a, const U& b) -> std::common_type_t { - return a + b; -} - -// 对于运算符 -template -class Point { - T x, y; -public: - template - auto operator+(const Point& other) const - -> Point> { - return {x + other.x, y + other.y}; - } -}; - -Point pi{1, 2}; -Point pd{3.5, 4.5}; -auto result = pi + pd; // Point{4.5, 6.5} -``` - -### 技巧4:C++20 三路比较运算符 - -C++20 大大简化了比较运算符的定义: - -```cpp -template -class Point { - T x, y; -public: - Point(T x, T y) : x(x), y(y) {} - - // 只需要定义一个运算符! - friend auto operator<=>(const Point&, const Point&) = default; -}; - -// 编译器自动生成: -// ==, !=, <, <=, >, >= -``` - -自定义三路比较: - -```cpp -template -class Point { - T x, y; -public: - Point(T x, T y) : x(x), y(y) {} - - friend auto operator<=>(const Point& a, const Point& b) { - if (auto cmp = a.x <=> b.x; cmp != 0) return cmp; - return a.y <=> b.y; - } - - // 三路比较不会自动生成==,需要单独定义 - friend bool operator==(const Point& a, const Point& b) { - return a.x == b.x && a.y == b.y; - } -}; -``` - -### 技巧5:约束运算符(C++20 Concepts) - -```cpp -template -concept Numeric = std::integral || std::floating_point; - -template -class Point { - T x, y; -public: - Point(T x, T y) : x(x), y(y) {} - - // 只有满足 Numeric 的类型才能比较 - friend auto operator<=>(const Point&, const Point&) = default; -}; - -Point pi; // ✅ -Point ps; // ❌ 编译错误 -``` - ------- - -## 实战:实现可比较的 Point - -现在让我们实现一个完整的、可比较的 `Point` 类型,综合运用本章学到的技巧。 - -### 需求定义 - -我们的 `Point` 应该: - -1. 支持任意数值类型(int、float、double等) -2. 支持所有比较运算符(==、!=、<、<=、>、>=) -3. 支持算术运算符(+、-、*、/) -4. 支持流输出运算符(<<) -5. 使用友元注入实现 -6. 提供类型安全的距离计算 - -### 完整实现 - -```cpp -#include -#include -#include -#include - -template -concept Numeric = std::integral || std::floating_point; - -template -class Point { - T x_, y_; - -public: - // 构造函数 - constexpr Point() : x_(0), y_(0) {} - - constexpr Point(T x, T y) : x_(x), y_(y) {} - - // Getter - constexpr T x() const { return x_; } - constexpr T y() const { return y_; } - - // Setter - constexpr void set_x(T x) { x_ = x; } - constexpr void set_y(T y) { y_ = y; } - - // ===== 比较运算符 ===== - - // C++20 三路比较(自动生成所有比较运算符) - constexpr friend auto operator<=>(const Point& a, const Point& b) { - if (auto cmp = a.x_ <=> b.x_; cmp != 0) return cmp; - return a.y_ <=> b.y_; - } - - constexpr friend bool operator==(const Point& a, const Point& b) { - return a.x_ == b.x_ && a.y_ == b.y_; - } - - // ===== 算术运算符 ===== - - constexpr friend Point operator+(const Point& a, const Point& b) { - return {a.x_ + b.x_, a.y_ + b.y_}; - } - - constexpr friend Point operator-(const Point& a, const Point& b) { - return {a.x_ - b.x_, a.y_ - b.y_}; - } - - constexpr friend Point operator*(const Point& p, T scalar) { - return {p.x_ * scalar, p.y_ * scalar}; - } - - constexpr friend Point operator*(T scalar, const Point& p) { - return p * scalar; - } - - constexpr friend Point operator/(const Point& p, T scalar) { - return {p.x_ / scalar, p.y_ / scalar}; - } - - // ===== 复合赋值运算符 ===== - - constexpr Point& operator+=(const Point& other) { - x_ += other.x_; - y_ += other.y_; - return *this; - } - - constexpr Point& operator-=(const Point& other) { - x_ -= other.x_; - y_ -= other.y_; - return *this; - } - - constexpr Point& operator*=(T scalar) { - x_ *= scalar; - y_ *= scalar; - return *this; - } - - constexpr Point& operator/=(T scalar) { - x_ /= scalar; - y_ /= scalar; - return *this; - } - - // ===== 流输出运算符 ===== - - friend std::ostream& operator<<(std::ostream& os, const Point& p) { - return os << '(' << p.x_ << ", " << p.y_ << ')'; - } - - // ===== 实用方法 ===== - - // 计算到原点的距离 - [[nodiscard]] constexpr double distance_from_origin() const { - return std::hypot(static_cast(x_), static_cast(y_)); - } - - // 计算到另一个点的距离 - [[nodiscard]] constexpr double distance_to(const Point& other) const { - double dx = static_cast(x_ - other.x_); - double dy = static_cast(y_ - other.y_); - return std::hypot(dx, dy); - } - - // 点积 - [[nodiscard]] constexpr T dot(const Point& other) const { - return x_ * other.x_ + y_ * other.y_; - } - - // 叉积(2D中返回标量) - [[nodiscard]] constexpr T cross(const Point& other) const { - return x_ * other.y_ - y_ * other.x_; - } - - // 判断是否为零点 - [[nodiscard]] constexpr bool is_zero() const { - return x_ == T{} && y_ == T{}; - } -}; - -// ===== 跨类型算术运算 ===== - -template -auto operator+(const Point& a, const Point& b) { - using Common = std::common_type_t; - return Point{ - static_cast(a.x()) + static_cast(b.x()), - static_cast(a.y()) + static_cast(b.y()) - }; -} - -template -auto operator-(const Point& a, const Point& b) { - using Common = std::common_type_t; - return Point{ - static_cast(a.x()) - static_cast(b.x()), - static_cast(a.y()) - static_cast(b.y()) - }; -} -``` - -### 使用示例 - -```cpp -#include -#include - -int main() { - // 基本构造 - Point p1{3, 4}; - Point p2{1, 2}; - - // 比较运算符 - assert(p1 == p1); - assert(p1 != p2); - assert(p1 > p2); // 按字典序比较 - - // 算术运算 - auto p3 = p1 + p2; // Point{4, 6} - auto p4 = p1 - p2; // Point{2, 2} - auto p5 = p1 * 2; // Point{6, 8} - auto p6 = p1 / 2; // Point{1, 2} - - // 复合赋值 - Point p7{5, 5}; - p7 += p2; // p7 变成 {6, 7} - - // 跨类型运算 - Point pi{10, 20}; - Point pd{1.5, 2.5}; - auto mixed = pi + pd; // Point{11.5, 22.5} - - // 输出 - std::cout << "p1 = " << p1 << '\n'; // p1 = (3, 4) - std::cout << "mixed = " << mixed << '\n'; // mixed = (11.5, 22.5) - - // 实用方法 - Point origin{0, 0}; - Point p{3, 4}; - std::cout << "Distance: " << p.distance_from_origin() << '\n'; // 5.0 - std::cout << "Dot product: " << p.dot(Point{1, 0}) << '\n'; // 3.0 - - return 0; -} -``` - -### 嵌入式优化版本 - -对于嵌入式环境,我们可能需要更轻量的实现: - -```cpp -#include - -template -class EmbeddedPoint { - T x_, y_; - -public: - constexpr EmbeddedPoint() : x_(0), y_(0) {} - constexpr EmbeddedPoint(T x, T y) : x_(x), y_(y) {} - - // 简化的比较(只实现 == 和 <) - constexpr friend bool operator==(const EmbeddedPoint& a, const EmbeddedPoint& b) { - return a.x_ == b.x_ && a.y_ == b.y_; - } - - constexpr friend bool operator<(const EmbeddedPoint& a, const EmbeddedPoint& b) { - return (a.x_ < b.x_) || (a.x_ == b.x_ && a.y_ < b.y_); - } - - // 内联算术运算 - constexpr EmbeddedPoint operator+(const EmbeddedPoint& other) const { - return {static_cast(x_ + other.x_), static_cast(y_ + other.y_)}; - } - - // 饱和加法(避免溢出) - constexpr EmbeddedPoint saturated_add(const EmbeddedPoint& other) const { - if constexpr (std::is_unsigned_v) { - T new_x = (x_ > std::numeric_limits::max() - other.x_) - ? std::numeric_limits::max() - : x_ + other.x_; - T new_y = (y_ > std::numeric_limits::max() - other.y_) - ? std::numeric_limits::max() - : y_ + other.y_; - return {new_x, new_y}; - } else { - return *this + other; // 有符号类型暂不支持 - } - } - - // 快速距离平方(避免浮点运算) - constexpr T distance_squared() const { - return x_ * x_ + y_ * y_; - } - - // 判断点是否在矩形内 - constexpr bool is_inside(T left, T top, T right, T bottom) const { - return x_ >= left && x_ <= right && y_ >= top && y_ <= bottom; - } -}; - -// 使用场景:图形界面、触摸屏检测 -using ScreenPoint = EmbeddedPoint; - -// 检测触摸点是否在按钮区域内 -constexpr bool is_touch_in_button(ScreenPoint touch, int16_t btn_x, - int16_t btn_y, int16_t btn_w, int16_t btn_h) { - return touch.is_inside(btn_x, btn_y, btn_x + btn_w, btn_y + btn_h); -} -``` - -### 使用 CRTP 的可比较基类版本 - -如果我们有多个类需要比较功能,可以使用 CRTP 基类: - -```cpp -template -class Comparable { -public: - // 三路比较 - friend auto operator<=>(const Comparable&, const Comparable&) = default; - - // 相等比较 - friend bool operator==(const Comparable& a, const Comparable& b) { - return static_cast(a).compare_impl( - static_cast(b) - ) == 0; - } - -protected: - ~Comparable() = default; -}; - -template -class Point : public Comparable, T> { - T x_, y_; - -public: - Point(T x, T y) : x_(x), y_(y) {} - - int compare_impl(const Point& other) const { - if (x_ < other.x_) return -1; - if (x_ > other.x_) return 1; - if (y_ < other.y_) return -1; - if (y_ > other.y_) return 1; - return 0; - } - - T x() const { return x_; } - T y() const { return y_; } -}; - -// 其他类也可以复用 -template -class Vector3D : public Comparable, T> { - T x_, y_, z_; - -public: - Vector3D(T x, T y, T z) : x_(x), y_(y), z_(z) {} - - int compare_impl(const Vector3D& other) const { - if (auto cmp = x_ <=> other.x_; cmp != 0) return cmp < 0 ? -1 : 1; - if (auto cmp = y_ <=> other.y_; cmp != 0) return cmp < 0 ? -1 : 1; - if (auto cmp = z_ <=> other.z_; cmp != 0) return cmp < 0 ? -1 : 1; - return 0; - } -}; -``` - ------- - -## 嵌入式应用场景 - -### 场景1:传感器数据比较 - -```cpp -template -class SensorReading { - T value_; - uint32_t timestamp_; - -public: - SensorReading(T value, uint32_t timestamp) - : value_(value), timestamp_(timestamp) {} - - // 按值比较(用于阈值检测) - friend bool operator==(const SensorReading& a, const SensorReading& b) { - return a.value_ == b.value_; - } - - friend auto operator<=>(const SensorReading& a, const SensorReading& b) { - return a.value_ <=> b.value_; - } - - // 按时间戳比较(用于排序) - friend bool chronological_order(const SensorReading& a, - const SensorReading& b) { - return a.timestamp_ < b.timestamp_; - } - - T value() const { return value_; } - uint32_t timestamp() const { return timestamp_; } -}; - -// 使用 -SensorReading temp1{25, 1000}; -SensorReading temp2{30, 1005}; - -if (temp2 > temp1) { - // 温度升高 -} -``` - -### 场景2:寄存器地址比较 - -```cpp -template -class Register { - AddrType address_; - DataType value_; - -public: - constexpr Register(AddrType addr, DataType val) - : address_(addr), value_(val) {} - - // 按地址比较(用于查找) - friend bool operator==(const Register& a, const Register& b) { - return a.address_ == b.address_; - } - - friend auto operator<=>(const Register& a, const Register& b) { - return a.address_ <=> b.address_; - } - - AddrType address() const { return address_; } - DataType value() const { return value_; } -}; - -// 使用 -using GPIOReg = Register; - -constexpr GPIOReg gpio_a{0x40020000, 0}; -constexpr GPIOReg gpio_b{0x40020400, 0}; - -if (gpio_a < gpio_b) { - // gpio_a 的地址更小 -} -``` - -### 场景3:配置参数验证 - -```cpp -template -class ConfigParameter { - const char* name_; - T value_; - T min_; - T max_; - -public: - constexpr ConfigParameter(const char* name, T val, T min_val, T max_val) - : name_(name), value_(val), min_(min_val), max_(max_val) { - // 编译期验证 - static_assert(min_val <= max_val, "Invalid range"); - } - - // 按名称比较 - friend bool operator==(const ConfigParameter& a, const ConfigParameter& b) { - return std::strcmp(a.name_, b.name_) == 0; - } - - // 按值比较 - friend bool operator<(const ConfigParameter& a, const ConfigParameter& b) { - return a.value_ < b.value_; - } - - constexpr bool is_valid() const { - return value_ >= min_ && value_ <= max_; - } - - const char* name() const { return name_; } - T value() const { return value_; } -}; -``` - -### 场景4:通信协议数据包比较 - -```cpp -template -class Packet { - SeqType sequence_; - PayloadSize size_; - uint8_t data_[256]; - -public: - Packet(SeqType seq, PayloadSize sz) : sequence_(seq), size_(sz) {} - - // 按序列号比较 - friend auto operator<=>(const Packet& a, const Packet& b) { - return a.sequence_ <=> b.sequence_; - } - - friend bool operator==(const Packet& a, const Packet& b) { - return a.sequence_ == b.sequence_; - } - - SeqType sequence() const { return sequence_; } - PayloadSize size() const { return size_; } -}; -``` - ------- - -## 常见陷阱与解决方案 - -### 陷阱1:友元函数不在全局作用域 - -```cpp -template -class Point { - friend bool operator==(const Point& a, const Point& b) { - return a.x == b.x && a.y == b.y; - } -}; - -Point p1, p2; -// operator==(p1, p2); // ❌ 可能找不到(取决于编译器) -p1 == p2; // ✅ 通过ADL找到 -``` - -**解决方案**:始终使用 `p1 == p2` 的形式,不要直接调用 `operator==`。 - -### 陷阱2:模板参数推导失败 - -```cpp -template -class Point { - template - friend Point operator+(const Point& a, const Point& b); -}; - -template -Point operator+(const Point& a, const Point& b) { - return {a.x + b.x, a.y + b.y}; // ❌ 无法访问私有成员 -} -``` - -**解决方案**:在类内定义友元函数,或使用公共访问器。 - -### 陷阱3:无限递归的 CRTP - -```cpp -template -class Base { -public: - void foo() { - static_cast(this)->foo(); // ❌ 无限递归! - } -}; - -class Derived : public Base { -public: - void foo() { - // 这里会调用 Base::foo,形成无限循环 - } -}; -``` - -**解决方案**:确保 `Derived::foo` 和 `Base::foo` 有不同的名称,或者使用 `this->foo()` 而不是强转后调用。 - -### 陷阱4:返回局部变量的引用 - -```cpp -template -class Point { - friend const Point& operator+(const Point& a, const Point& b) { - Point result{a.x + b.x, a.y + b.y}; // ❌ 局部变量 - return result; // ❌ 返回局部变量的引用! - } -}; -``` - -**解决方案**:返回值而非引用: - -```cpp -friend Point operator+(const Point& a, const Point& b) { - return {a.x + b.x, a.y + b.y}; // ✅ 返回值(可能被RVO优化) -} -``` - -### 陷阱5:C++20 三路比较的默认实现 - -```cpp -template -class Point { - T x, y; -public: - // 默认的 operator<=> 会逐成员比较 - friend auto operator<=>(const Point&, const Point&) = default; -}; - -Point p1, p2; -// p1 == p2; // ❌ 指针比较,不是值比较! -``` - -**解决方案**:为指针类型自定义比较,或禁用指针类型的实例化。 - -```cpp -template -class Point { // 使用 Concept 约束 - T x, y; -public: - friend auto operator<=>(const Point&, const Point&) = default; -}; -``` - -### 陷阱6:友元函数的模板参数推导 - -```cpp -template -class Point { - template - friend bool operator==(const Point&, const Point&); - // ⚠️ 这会让 Point 和 Point 也能比较 - // 但可能不是你想要的! -}; -``` - -**解决方案**:使用 `std::same_as` 约束或使用非模板友元。 - -```cpp -// 方案1:非模板友元(推荐) -template -class Point { - friend bool operator==(const Point& a, const Point& b) { - return a.x == b.x && a.y == b.y; - } -}; - -// 方案2:C++20 约束 -template -class Point { - template - friend bool operator==(const Point& a, const Point& b) - requires std::same_as { - return a.x == b.x && a.y == b.y; - } -}; -``` - ------- - -## C++20 新特性:Spaceship 运算符 - -C++20 的三路比较运算符(`operator<=>`,俗称 Spaceship 运算符)彻底改变了比较运算符的写法。 - -### 默认生成 - -```cpp -template -class Point { - T x, y; -public: - // 一行代码,自动生成 ==、!=、<、<=、>、>= - friend auto operator<=>(const Point&, const Point&) = default; -}; -``` - -### 自定义实现 - -```cpp -template -class Point { - T x, y; -public: - friend auto operator<=>(const Point& a, const Point& b) { - if (auto cmp = a.x <=> b.x; cmp != 0) return cmp; - return a.y <=> b.y; - } - - // 三路比较不会自动生成 ==,需要单独定义 - friend bool operator==(const Point& a, const Point& b) { - return a.x == b.x && a.y == b.y; - } -}; -``` - -### 比较类别 - -`operator<=>` 返回不同的比较类别: - -| 返回类型 | 说明 | 示例类型 | -|---------|------|----------| -| `std::strong_ordering` | 完全可替换 | `int`、`std::string` | -| `std::weak_ordering` | 等价但可替换 | `float`(NaN) | -| `std::partial_ordering` | 部分可比较 | 复数(只有相等才有意义) | - -```cpp -#include - -template -class Point { - T x, y; -public: - // 指定返回类型 - friend std::strong_ordering operator<=>(const Point& a, const Point& b) { - if (auto cmp = a.x <=> b.x; cmp != 0) return cmp; - return a.y <=> b.y; - } -}; -``` - -### 同类和异类比较 - -C++20 支持为同类和异类比较分别定义运算符: - -```cpp -template -class Point { - T x, y; -public: - // 同类比较 - friend auto operator<=>(const Point&, const Point&) = default; - - // 异类比较(用默认的 rewritable 方式) - template - friend auto operator<=>(const Point& a, const Point& b) { - if (auto cmp = a.x <=> b.x; cmp != 0) return cmp; - return a.y <=> b.y; - } -}; -``` - ------- - -## 性能考量 - -### 编译期开销 - -友元注入和 Barton-Nackman Trick 的编译期开销主要来自: - -1. **模板实例化**:每个类型组合都会生成新代码 -2. **符号表膨胀**:大量友元函数会增加符号表 -3. **ADL 查找**:编译器需要额外进行 ADL 查找 - -### 运行时开销 - -正确使用下,**零运行时开销**: - -```cpp -// 编译前 -Point p1{1, 2}, p2{3, 4}; -bool result = (p1 == p2); - -// 编译后(近似) -bool result = (p1.x == p2.x && p1.y == p2.y); -// 完全内联,无函数调用 -``` - -### 优化建议 - -1. **对于小型类**:使用友元注入,代码简洁 -2. **对于大型层次结构**:使用 CRTP 基类复用代码 -3. **对于 C++20**:优先使用 `operator<=>` 默认实现 -4. **限制实例化**:使用 Concepts 约束模板参数 - -```cpp -// ✅ 好:使用 Concepts 限制 -template -class Point { /* ... */ }; - -// ❌ 不好:对任何类型都实例化 -template -class Point { /* ... */ }; -``` - -1. **使用 `constexpr`**:鼓励编译期计算 - -```cpp -constexpr Point p1{1, 2}; -constexpr Point p2{3, 4}; -constexpr bool eq = (p1 == p2); // 编译期计算 -static_assert(!eq); -``` - ------- - -## 在线运行 - -在线体验友元注入的运算符重载、ADL 查找和 C++20 spaceship 运算符: - - - -## 小结 - -本章我们深入探讨了模板友元与 Barton-Nackman Trick: - -### 核心概念 - -| 概念 | 作用 | 使用场景 | -|------|------|----------| -| 友元注入 | 在类内定义友元函数,通过ADL可在外部调用 | 简化运算符重载 | -| Barton-Nackman Trick | 约束运算符只在特定类型下可用 | 早期约束泛型编程 | -| CRTP | 派生类作为基类的模板参数 | 共享基类逻辑 | -| 三路比较 | C++20统一比较运算符 | 简化比较运算符定义 | - -### 实战要点 - -1. **运算符重载选择**: - - `==`、`!=`、`<`、`<=`、`>`、`>=`:友元函数 - - `+=`、`-=`、`*=`、`/=`:成员函数 - - C++20:使用 `operator<=>` - -2. **嵌入式优化**: - - 使用 `constexpr` 编译期计算 - - 避免浮点运算,使用整数距离平方 - - 使用 Concepts 约束减少实例化 - -3. **常见陷阱**: - - 友元函数只能通过 ADL 找到 - - 返回值而非引用 - - CRTP 避免无限递归 - -4. **现代C++推荐**: - - 简单场景:直接友元注入 - - 复杂场景:CRTP 基类 - - C++20:`operator<=>` 默认实现 + Concepts - -**下一章**,我们将探讨**模板元编程进阶**,学习 SFINAE、类型萃取、标签分发等高级技巧,并实现一个编译期反射系统。 diff --git a/documents/vol4-advanced/07-template-aliases-and-using.md b/documents/vol4-advanced/07-template-aliases-and-using.md deleted file mode 100644 index 3ff3dd021..000000000 --- a/documents/vol4-advanced/07-template-aliases-and-using.md +++ /dev/null @@ -1,1315 +0,0 @@ ---- -chapter: 12 -cpp_standard: -- 11 -- 14 -- 17 -- 20 -description: 掌握现代C++类型别名的强大功能与标准库实现原理 -difficulty: intermediate -order: 7 -platform: host -prerequisites: -- 'Chapter 12: 类模板详解' -- 'Chapter 12: 模板元编程基础' -reading_time_minutes: 35 -tags: -- cpp-modern -- host -- intermediate -title: 模板别名与Using声明 ---- -# 嵌入式现代C++教程——模板别名与Using声明 - -你有没有被这种代码折磨过? - -```cpp -std::map>::iterator it = sensors.begin(); - -// 或者更可怕的 -typename std::enable_if::value, T>::type result = process(value); -``` - -这种代码不仅难读,还容易出错。C++11引入的`using`声明和别名模板正是解决这个问题的利器。本章我们将深入探讨类型别名的现代写法,追溯标准库中`conditional_t`、`enable_if_t`的设计原理,并学习如何用别名模板简化复杂的模板类型声明。 - ------- - -## 从 typedef 到 using - -### typedef 的前世今生 - -在C++11之前,我们只能用`typedef`来定义类型别名: - -```cpp -// 基本用法 -typedef unsigned int uint32_t; -typedef int int_array[10]; -typedef void (*function_ptr)(int); - -// 复杂一点的 -typedef std::map> StringToIntVectorMap; -``` - -`typedef`的问题很明显: - -1. **语法不一致**:`typedef`声明的语法与变量声明类似,但关键字位置别扭 -2. **不支持模板**:无法为模板类型定义别名 -3. **可读性差**:复杂类型别名难以解析 - -```cpp -// 这是什么? -typedef void (*SignalHandler)(int, siginfo_t*, void*); - -// 需要从右向左读:SignalHandler是一个函数指针类型, -// 指向的函数接受(int, siginfo_t*, void*)参数,返回void -``` - -### using 的优雅登场 - -C++11引入的`using`声明提供了更清晰的语法: - -```cpp -// 基本用法 -using uint32_t = unsigned int; -using int_array = int[10]; -using function_ptr = void(*)(int); - -// 语法更自然:从左向右读 -using SignalHandler = void(*)(int, siginfo_t*, void*); -``` - -**关键优势**:`using`语法的"别名 = 原类型"结构更符合直觉,与变量初始化的写法一致。 - -### typedef vs using 对比表 - -| 特性 | typedef | using | -|------|---------|-------| -| 基本类型别名 | 支持 | 支持 | -| 函数指针别名 | 支持 | 支持(更清晰) | -| 模板别名 | 不支持 | 支持 | -| 模板模板参数 | 困难 | 直观 | -| 作用域 | 同 | 同 | -| 可读性 | 较差 | 更好 | - -### 函数指针别名对比 - -```cpp -// typedef 写法 -typedef void (*OldStyleCallback)(int error_code, const char* message); -OldStyleCallback cb1 = nullptr; - -// using 写法 -using NewStyleCallback = void(*)(int error_code, const char* message); -NewStyleCallback cb2 = nullptr; - -// 多个参数的函数指针 -typedef void (*ComplexOld)(int, double, const std::string&); -using ComplexNew = void(*)(int, double, const std::string&); - -// 返回函数指针的函数 -// typedef 版本(非常晦涩) -typedef void (*FuncPtr)(int); -FuncPtr getFunc typedef_Old(int id); - -// using 版本(相对清晰) -using FuncPtr = void(*)(int); -FuncPtr getFunc_New(int id); - -// 返回函数指针的函数指针...你能看出区别吗? -using CallbackFactory = auto(*)(int) -> void(*)(int); -``` - ------- - -## 别名模板(Alias Templates) - -### typedef 的致命缺陷 - -`typedef`最大的问题是**无法为模板定义别名**: - -```cpp -template -struct MyAllocator { - using value_type = T; - T* allocate(std::size_t n); - void deallocate(T* p, std::size_t n); -}; - -// ❌ typedef 无法做到 -// typedef MyAllocator MyAlloc; // 错误:T 未定义 - -// 只能为具体类型别名 -typedef MyAllocator MyIntAllocator; // 只适用于 int -``` - -这导致大量重复代码,每次使用不同类型都要重新声明。 - -### using 别名模板 - -C++11的`using`完美解决了这个问题: - -```cpp -// ✅ 别名模板 -template -using MyAlloc = MyAllocator; - -// 使用 -MyAlloc int_alloc; -MyAlloc double_alloc; -MyAlloc string_alloc; -``` - -**关键点**:别名模板**不是新类型的定义**,而是现有类型的语法糖。模板实例化时会直接替换为原类型。 - -### 实际应用:简化标准库容器 - -```cpp -// 嵌入式常用配置 -template -using FixedVector = std::vector>; - -// 使用 -FixedVector rx_buffer; // 32字节接收缓冲区 -FixedVector adc_samples; // 16个ADC采样 - -// 更复杂的例子 -template -using StringMap = std::map, PoolAllocator<64>>; - -StringMap settings; -``` - -### 别名模板 vs 特化 - -别名模板**不能被特化**,这是它与类模板的重要区别: - -```cpp -// 别名模板(不能特化) -template -using Ptr = T*; - -// ❌ 错误:别名模板不能特化 -// template<> -// using Ptr = void*; - -// 如果需要特化,使用类模板 -template -struct PtrTraits { - using type = T*; -}; - -// 可以特化 -template<> -struct PtrTraits { - using type = void*; -}; - -// 使用 -template -using SafePtr = typename PtrTraits::type; -``` - -### 嵌入式场景:外设类型别名 - -```cpp -// HAL 层类型定义 -template -struct Register { - static constexpr RegType address = Address; - using value_type = RegType; - - RegType read() const; - void write(RegType value); -}; - -// 为常用寄存器创建别名 -using GPIOA_MODER = Register; -using GPIOA_ODR = Register; -using SPI1_DR = Register; - -// 使用 -void init_gpio() { - GPIOA_MODER moder; - moder.write(0xABCDFFFF); -} -``` - ------- - -## 标准库溯源:_t 后缀的由来 - -你一定见过很多`_t`后缀的类型别名: - -```cpp -std::size_t -std::int32_t -std::make_signed_t -std::enable_if_t -std::conditional_t -``` - -这些其实是**别名模板**的命名约定——`_t`表示"类型"(type)。让我们追溯它们的设计原理。 - -### std::enable_if_t 的演变 - -#### C++11/14 原始版本 - -```cpp -// 标准库实现 -template -struct enable_if { - // 空:当 Condition 为 false 时 -}; - -template -struct enable_if { - using type = T; // 只有 Condition 为 true 时才有 type 成员 -}; - -// 使用方式(冗长) -template -typename std::enable_if::value, T>::type -process(T value) { - return value * 2; -} -``` - -#### C++14 别名模板简化 - -```cpp -// C++14 添加的别名模板 -template -using enable_if_t = typename enable_if::type; - -// 使用方式(简洁!) -template -std::enable_if_t, T> -process(T value) { - return value * 2; -} -``` - -#### 实现原理剖析 - -```cpp -// 逐步剖析 -template -using enable_if_t = typename enable_if::type; -// ^^^^^^^^^^^^^^^^^^^^^^^^^^ -// 这部分引用原始 struct 的 type 成员 - -// 当 B = true 时 -// enable_if::type = int -// 所以 enable_if_t = int - -// 当 B = false 时 -// enable_if 没有 type 成员 -// 所以 enable_if_t 替换失败,触发 SFINAE -``` - -### std::conditional_t 的设计 - -#### C++11/14 原始版本 - -```cpp -// 标准库实现 -template -struct conditional { - using type = TrueType; // Condition 为 true 时的默认情况 -}; - -template -struct conditional { - using type = FalseType; // Condition 为 false 时的特化 -}; - -// 使用方式(冗长) -template -void process(typename std::conditional< - std::is_integral::value, - std::integral_constant, - std::integral_constant ->::type tag); -``` - -#### C++14 别名模板简化 - -```cpp -// C++14 添加的别名模板 -template -using conditional_t = typename conditional::type; - -// 使用方式(简洁!) -template -void process(std::conditional_t< - std::is_integral_v, - std::integral_constant, - std::integral_constant -> tag); -``` - -#### 实际应用示例 - -```cpp -// 根据类型大小选择最优算法 -template -using FastCopy = std::conditional_t< - sizeof(T) == 1, // 如果是 1 字节 - void(*)(uint8_t*, const uint8_t*, std::size_t), // 使用 memcpy - void(*)(T*, const T*, std::size_t) // 否则使用循环 ->; - -// 嵌入式场景:根据架构选择指令 -template -using OptimalAdd = std::conditional_t< - std::is_integral_v && sizeof(T) <= 4, - std::integral_constant, // 使用单周期加法 - std::integral_constant // 需要多周期或库调用 ->; -``` - -### 常用的 _t 别名模板 - -| 别名模板 | 说明 | 示例 | -|---------|------|------| -| `enable_if_t` | 条件启用类型 | `std::enable_if_t>` | -| `conditional_t` | 条件选择类型 | `std::conditional_t` | -| `remove_reference_t` | 移除引用 | `std::remove_reference_t` = `int` | -| `add_const_t` | 添加 const | `std::add_const_t` = `const int` | -| `make_signed_t` | 转为有符号 | `std::make_signed_t` = `int` | -| `make_unsigned_t` | 转为无符号 | `std::make_unsigned_t` = `unsigned int` | -| `common_type_t` | 公共类型 | `std::common_type_t` = `double` | -| `decay_t` | 衰退类型 | `std::decay_t` = `int*` | -| `invoke_result_t` | 调用结果类型 | `std::invoke_result_t` | - ------- - -## 实战:简化复杂模板类型声明 - -让我们通过几个实际场景展示别名模板的威力。 - -### 场景1:类型安全的通信协议 - -```cpp -// 协议基础类型 -template -struct BigEndian { - T value; - BigEndian() = default; - explicit BigEndian(T v) : value(htobe(v)) {} - T get() const { return betoh(value); } -}; - -// 为常用大小创建别名 -using BE16 = BigEndian; -using BE32 = BigEndian; -using BE64 = BigEndian; - -// 协议头部 -struct PacketHeader { - BE16 sync; // 同步字 - BE16 length; // 长度 - BE32 timestamp; // 时间戳 - BE16 checksum; // 校验和 -}; - -// 解析函数 -void parse_packet(const uint8_t* buffer) { - auto header = reinterpret_cast(buffer); - uint16_t len = header->length.get(); // 自动转换字节序 - // ... -} -``` - -### 场景2:嵌入式回调系统 - -```cpp -// 回调类型定义 -template -using Callback = void(*)(Args...); - -// 具体回调别名 -using ErrorHandler = Callback; -using DataHandler = Callback; -using TimeoutHandler = Callback; - -// 回调管理器 -template -class CallbackManager { -public: - using CallbackType = Callback; - using Token = std::size_t; - - Token register_callback(CallbackType cb) { - Token token = next_token_++; - callbacks_[token] = cb; - return token; - } - - void invoke(Args... args) { - for (auto& [token, cb] : callbacks_) { - if (cb) cb(args...); - } - } - -private: - std::map callbacks_; - Token next_token_ = 0; -}; - -// 使用 -void error_callback(int code, const char* msg) { - log_error("Error %d: %s", code, msg); -} - -// 注册 -CallbackManager error_manager; -auto token = error_manager.register_callback(error_callback); -``` - -### 场景3:内存池配置 - -```cpp -// 内存池特征 -template -struct PoolTraits { - static constexpr std::size_t block_size = BlockSize; - static constexpr std::size_t blocks = Blocks; - static constexpr std::size_t total_size = BlockSize * Blocks; - using block_type = std::aligned_storage_t; -}; - -// 常用配置 -using SmallPool = PoolTraits<32, 64>; // 2KB -using MediumPool = PoolTraits<128, 32>; // 4KB -using LargePool = PoolTraits<512, 16>; // 8KB - -// 内存池实现 -template -class MemoryPool { -public: - using BlockType = typename Traits::block_type; - - void* allocate() { - if (free_list_) { - auto block = free_list_; - free_list_ = *static_cast(block); - return block; - } - return nullptr; - } - - void deallocate(void* ptr) { - auto block = static_cast(ptr); - *block = free_list_; - free_list_ = block; - } - -private: - BlockType storage_[Traits::blocks]; - void* free_list_ = nullptr; -}; - -// 使用 -MemoryPool small_objects; -MemoryPool medium_objects; -``` - -### 场景4:智能指针类型别名 - -```cpp -// 项目统一的智能指针配置 -template -using UniquePtr = std::unique_ptr>; - -template -using SharedPtr = std::shared_ptr; - -// 线程安全的智能指针 -template -using ThreadSafePtr = std::shared_ptr; - -// 单例智能指针 -template -using SingletonPtr = std::unique_ptr; - -// 外设指针(硬件寄存器) -template -using PeripheralPtr = std::uintptr_t; - -// 使用 -class UARTDriver { -public: - static UniquePtr instance() { - return UniquePtr(new UARTDriver()); - } -}; - -auto uart = UARTDriver::instance(); -``` - -### 场景5:传感器接口类型 - -```cpp -// 传感器读取结果 -template -struct SensorResult { - T value; - bool valid; - uint32_t timestamp; -}; - -// 常用传感器结果类型 -using TemperatureResult = SensorResult; -using PressureResult = SensorResult; -using AccelerationResult = SensorResult>; - -// 传感器接口 -template -class ISensor { -public: - using Result = ResultType; - - virtual Result read() = 0; - virtual bool calibrate() = 0; -}; - -// 具体传感器 -class TempSensor : public ISensor { -public: - Result read() override { - Result r; - r.value = read_temperature(); - r.valid = true; - r.timestamp = get_timestamp(); - return r; - } -}; -``` - -### 场景6:类型特征组合 - -```cpp -// 检测类型是否适合作为 DMA 缓冲区 -template -using is_dma_capable = std::conjunction< - std::is_trivially_copyable, - std::has_unique_object_representations, - std::bool_constant<(alignof(T) >= 4)> ->; - -// 获取最佳 DMA 传输类型 -template -using dma_transfer_type = std::conditional_t< - is_dma_capable::value, - T, // 直接传输 - std::aligned_storage_t // 需要拷贝 ->; - -// 使用示例 -template -void dma_transfer(const T* src, T* dst, std::size_t count) { - using TransferType = dma_transfer_type; - - if constexpr (is_dma_capable::value) { - // 直接 DMA 传输 - start_dma(src, dst, count * sizeof(T)); - } else { - // 需要先对齐 - std::array buffer; - // 拷贝到缓冲区,然后 DMA - } -} -``` - ------- - -## 高级技巧:编译期类型选择 - -### 技巧1:递归类型构建 - -```cpp -// 构建固定大小的整数类型 -template -struct IntOfSize { - static_assert(Bits <= 64, "Type too large"); - using type = std::conditional_t< - Bits <= 8, uint8_t, - std::conditional_t< - Bits <= 16, uint16_t, - std::conditional_t< - Bits <= 32, uint32_t, - uint64_t - > - > - >; -}; - -template -using IntOfSize_t = typename IntOfSize::type; - -// 使用 -IntOfSize_t<12> value = 0xFFF; // 使用 uint16_t -IntOfSize_t<24> data = 0xFFFFFF; // 使用 uint32_t -``` - -### 技巧2:SFINAE 友好的类型检测 - -```cpp -// 检测类型是否有 serialize 方法 -template -struct has_serialize : std::false_type {}; - -template -struct has_serialize().serialize())>> : std::true_type {}; - -template -using has_serialize_t = typename has_serialize::type; - -template -constexpr bool has_serialize_v = has_serialize::value; - -// 使用 -template -std::enable_if_t, std::string> -to_string(const T& obj) { - return obj.serialize(); -} - -template -std::enable_if_t, std::string> -to_string(const T& obj) { - return std::to_string(obj); -} -``` - -### 技巧3:策略选择别名 - -```cpp -// 根据类型选择最优策略 -template -using OptimizedCopy = std::conditional_t< - std::is_trivially_copyable_v, - std::integral_constant, // memcpy - std::conditional_t< - std::is_pointer_v, - std::integral_constant, // 指针拷贝 - std::integral_constant // 逐元素拷贝 - > ->; - -// 策略实现 -template -void copy_optimized(T* dst, const T* src, std::size_t n) { - constexpr auto strategy = OptimizedCopy::value; - - if constexpr (strategy == 0) { - std::memcpy(dst, src, n * sizeof(T)); - } else if constexpr (strategy == 1) { - for (std::size_t i = 0; i < n; ++i) { - dst[i] = src[i]; - } - } else { - std::copy_n(src, n, dst); - } -} -``` - -### 技巧4:延迟类型计算 - -```cpp -// 延迟计算类型(避免实例化不完整的类型) -template -struct LazyType { - using type = T; -}; - -template -using LazyType_t = typename LazyType::type; - -// 使用场景:前向声明 -template -class Container { - using iterator = LazyType_t, - const T*, - T* - >::type>; -}; -``` - ------- - -## 嵌入式最佳实践 - -### 实践1:硬件寄存器类型别名 - -```cpp -// 寄存器宽度定义 -using Reg8 = volatile uint8_t; -using Reg16 = volatile uint16_t; -using Reg32 = volatile uint32_t; - -// 位域定义 -template -struct BitField { - Reg& reg; - static constexpr BitType mask = BitType::mask; - static constexpr uint8_t offset = BitType::offset; - - void set() { reg |= mask; } - void clear() { reg &= ~mask; } - bool is_set() const { return reg & mask; } - BitType get() const { return static_cast((reg >> offset) & mask); } -}; - -// 使用 -using GPIO_PIN5 = BitField; -``` - -### 实践2:编译期缓冲区配置 - -```cpp -// 缓冲区配置 -template -struct BufferConfig { - static constexpr std::size_t size = Size; - static constexpr std::size_t alignment = (Size >= 64) ? 8 : 4; - using storage_type = std::aligned_storage_t; -}; - -// 常用配置 -using RxBufferConfig = BufferConfig<256>; -using TxBufferConfig = BufferConfig<512>; -using LogBufferConfig = BufferConfig<1024>; - -// 缓冲区实现 -template -class Buffer { - alignas(Config::alignment) typename Config::storage_type storage_; -public: - // ... -}; -``` - -### 实践3:DMA 描述符类型 - -```cpp -// DMA 描述符 -struct DMADescriptor { - std::uintptr_t src_addr; - std::uintptr_t dst_addr; - std::uint32_t control; - std::uint32_t reserved; -}; - -// DMA 通道类型别名 -using DMADescriptorArray = std::array; -using DMADescriptorPtr = std::uintptr_t; // 物理地址 -``` - -### 实践4:错误码类型安全 - -```cpp -// 错误码基类 -template -class ErrorCode { -public: - using value_type = Underlying; - - constexpr ErrorCode() : value_(0) {} - constexpr explicit ErrorCode(value_type v) : value_(v) {} - - constexpr value_type value() const { return value_; } - constexpr bool operator==(const ErrorCode& other) const { return value_ == other.value_; } - constexpr bool operator!=(const ErrorCode& other) const { return value_ != other.value_; } - -private: - value_type value_; -}; - -// 具体错误码类型 -using IOError = ErrorCode; -using SPIError = ErrorCode; -using I2CError = ErrorCode; - -// 使用 -I2CError i2c_write(uint8_t addr, const uint8_t* data, std::size_t len); -``` - ------- - -## 常见陷阱 - -### 陷阱1:别名模板不是类型 - -```cpp -template -using Ptr = T*; - -// ❌ 错误:不能前向声明别名模板 -// template -// using Ptr; - -// ✅ 正确:直接使用 -Ptr p; - -// ❌ 错误:不能特化别名模板 -// template<> -// using Ptr = void*; -``` - -### 陷阱2:依赖类型的 typename - -```cpp -template -class Container { - // ❌ 错误:需要 typename - // using ValueType = T::value_type; - - // ✅ 正确 - using ValueType = typename T::value_type; - - // C++20 可以省略(如果确定是类型) - // using ValueType = T::value_type; -}; -``` - -### 陷阱3:别名与 typedef 的可见性 - -```cpp -class Base { -protected: - using value_type = int; -}; - -class Derived : public Base { - // typedef 的行为不同 - // typedef Base::value_type value_type; // 需要显式 - - // using 可以直接引入 - using Base::value_type; // OK -}; -``` - -### 陷阱4:函数类型别名 - -```cpp -// ❌ 混淆的写法 -// using Func = void(); // 这是函数类型,不是函数指针 - -// ✅ 正确的函数指针别名 -using FuncPtr = void(*)(); - -// 函数类型(用于模板参数) -template -void call(F f) { f(); } - -// 使用 -void my_func() {} -call(my_func); // OK,函数退化为指针 -call(my_func); // 也可以 -``` - -### 陷阱5:模板模板参数 - -```cpp -template -using MyVector = std::vector>; - -// ❌ 错误:MyVector 不是模板模板参数 -// template class Container> -// void process(Container c); - -// ✅ 正确:使用实际的模板 -template class Container> -void process(Container> c); -``` - ------- - -## C++14/17/20 新特性 - -### C++14:更多 _t 别名 - -```cpp -// C++11/14 标准 -using enable_if_t = typename enable_if<...>::type; -using conditional_t = typename conditional<...>::type; -using remove_reference_t = typename remove_reference<...>::type; - -// 可以自己定义 -template -using my_type_t = typename MyTrait::type; -``` - -### C++14:变量模板与别名 - -```cpp -// 变量模板 -template -constexpr bool is_integral_v = std::is_integral::value; - -// 配合别名模板使用 -template -using enable_if_integral = std::enable_if_t>; - -// 使用 -template> -void process(T t); -``` - -### C++17:std::void_t - -```cpp -// C++17 标准 -template -using void_t = void; - -// SFINAE 检测 -template -struct has_member : std::false_type {}; - -template -struct has_member> : std::true_type {}; -``` - -### C++20:Concepts 与别名 - -```cpp -// Concept 定义 -template -concept Integral = std::is_integral_v; - -// 与别名模板配合 -template -using EnableIfIntegral = std::enable_if_t>; - -// 或者直接用 Concepts -template -void process(T t); - -// 缩写函数模板 -void process2(Integral auto t); -``` - -### C++20:requires 表达式 - -```cpp -template -concept Iterable = requires(T t) { - { t.begin() } -> std::same_as; - { t.end() } -> std::same_as; -}; - -// 使用 -template -void process(T container); -``` - ------- - -## 实战:构建完整的类型系统 - -让我们用别名模板构建一个嵌入式项目中常用的类型系统。 - -::: details 点击展开完整代码 - -```cpp - -#include - -#include - -#include - -#include - -// ==================== 基础类型别名 ==================== - -// 寄存器类型 -using Reg8 = std::uint8_t; -using Reg16 = std::uint16_t; -using Reg32 = std::uint32_t; - -// 大小类型 -using SizeType = std::size_t; - -// 时间戳类型 -using Timestamp = std::uint32_t; - -// ==================== 类型特征别名 ==================== - -// 检测是否为 POD 类型 -template -using IsPOD = std::is_pod; - -template -constexpr bool IsPOD_v = IsPOD::value; - -// 检测是否为标准布局 -template -using IsStandardLayout = std::is_standard_layout; - -template -constexpr bool IsStandardLayout_v = IsStandardLayout::value; - -// ==================== 条件类型选择 ==================== - -// DMA 传输类型 -template -using DMATransferType = std::conditional_t< - IsPOD_v && sizeof(T) <= 4, - T, - std::aligned_storage_t ->; - -// 中断处理函数类型 -template -using IRQHandler = void(*)(Args...); - -// ==================== 容器类型别名 ==================== - -// 固定大小数组 -template -using FixedArray = std::array; - -// 常用缓冲区大小 -template -using RxBuffer = FixedArray; - -template -using TxBuffer = FixedArray; - -template -using LogBuffer = FixedArray; - -// ==================== 智能指针别名 ==================== - -// 项目统一的智能指针配置 -template -using UniquePtr = std::unique_ptr; - -template -using SharedPtr = std::shared_ptr; - -// 单例指针 -template -using SingletonPtr = std::unique_ptr; - -// ==================== 错误处理类型 ==================== - -// 错误码基类 -template -class ErrorCode { -public: - using value_type = Underlying; - - constexpr ErrorCode() : value_(0) {} - constexpr explicit ErrorCode(value_type v) : value_(v) {} - - constexpr value_type value() const { return value_; } - -private: - value_type value_; -}; - -// 具体错误码类型 -using IOError = ErrorCode; -using SPIError = ErrorCode; -using I2CError = ErrorCode; -using ADCError = ErrorCode; - -// ==================== 传感器类型 ==================== - -// 传感器结果 -template -struct SensorResult { - T value; - bool valid; - Timestamp timestamp; -}; - -// 常用传感器结果类型 -using TemperatureResult = SensorResult; -using PressureResult = SensorResult; -using HumidityResult = SensorResult; -using AccelerationResult = SensorResult>; - -// 传感器接口 -template -class ISensor { -public: - using Result = ResultType; - - virtual ~ISensor() = default; - virtual Result read() = 0; - virtual bool calibrate() = 0; -}; - -// ==================== 回调类型 ==================== - -// 错误回调 -template -using ErrorCallback = void(*)(Args...); - -using SystemErrorCallback = ErrorCallback; - -// 数据回调 -template -using DataCallback = void(*)(const T&, Timestamp); - -// 完成回调 -using CompletionCallback = void(*)(bool success); - -// ==================== 配置类型 ==================== - -// UART 配置 -struct UARTConfig { - std::uint32_t baudrate; - std::uint8_t data_bits; - std::uint8_t stop_bits; - bool parity; -}; - -// SPI 配置 -struct SPIConfig { - std::uint32_t baudrate; - std::uint8_t mode; - bool lsb_first; -}; - -// I2C 配置 -struct I2CConfig { - std::uint32_t clock_speed; - std::uint16_t timeout_ms; -}; - -// ==================== 使用示例 ==================== - -class TemperatureSensor : public ISensor { -public: - Result read() override { - Result r; - r.value = 25.5f; - r.valid = true; - r.timestamp = 12345; - return r; - } - - bool calibrate() override { - return true; - } -}; - -void example_usage() { - // 使用智能指针 - auto sensor = UniquePtr>( - new TemperatureSensor() - ); - - // 读取传感器 - auto result = sensor->read(); - - // 使用缓冲区 - RxBuffer rx_data; - TxBuffer tx_data; - - // 使用错误码 - I2CError error = I2CError(1); - - // 使用回调 - SystemErrorCallback error_cb = [](int code, const char* msg) { - // 处理错误 - }; -} - -``` - -::: - ------- - -## 性能分析 - -### 编译期 vs 运行期 - -| 特性 | 编译期开销 | 运行期开销 | -|------|-----------|-----------| -| typedef | 无 | 无 | -| using 别名 | 无 | 无 | -| 别名模板 | 实例化时间 | 无 | -| _t 后缀类型 | 实例化时间 | 无 | - -**结论**:别名模板**没有运行时开销**,所有计算都在编译期完成。 - -### 代码大小 - -```cpp - -// 测试代码大小 -template -using Ptr = T*; - -Ptr p1; -Ptr p2; - -// 生成的代码与以下完全相同: -int* p1; -double* p2; - -// 别名模板只是语法糖,不产生额外代码 -``` - -### 编译时间影响 - -```cpp -// 简单别名:编译时间可忽略 -using MyInt = int; - -// 别名模板:轻微增加编译时间 -template -using MyVector = std::vector; - -// 复杂的类型特征:可能显著增加编译时间 -template -using ComplexType = std::conditional_t< - std::conjunction_v, B, C>, - std::enable_if_t::value, E>, - F ->; -``` - -**建议**:在头文件中保持别名模板简单,复杂的类型计算放在源文件中。 - ------- - -## 小结 - -别名模板是现代C++类型系统的重要组成部分: - -| 特性 | typedef | using | -|------|---------|-------| -| 基本别名 | 支持 | 支持(更清晰) | -| 函数指针 | 支持 | 支持(更清晰) | -| 模板别名 | **不支持** | **支持** | -| 模板模板参数 | 困难 | 直观 | -| 可读性 | 较差 | 更好 | - -**核心要点**: - -1. **优先使用 `using`**:语法更清晰,功能更强大 -2. **理解 `_t` 后缀约定**:`enable_if_t`、`conditional_t` 等都是别名模板 -3. **别名模板是语法糖**:不产生运行时开销,只在编译期展开 -4. **结合类型特征使用**:与 `type_traits` 配合实现编译期类型选择 -5. **简化复杂类型**:将复杂的模板类型封装成易读的别名 - -**实践建议**: - -1. 项目中统一使用 `using` 而非 `typedef` -2. 为常用的模板类型定义别名模板 -3. 使用 `_t` 后缀约定标记类型别名 -4. 头文件中保持别名模板简单,避免过度嵌套 -5. 利用别名模板实现类型安全的接口 - -**下一章**,我们将探讨 **可变参数模板**,学习如何处理任意数量的模板参数,实现类型安全的格式化函数和灵活的委托系统。 diff --git a/documents/vol4-advanced/08-templates-and-inheritance-crtp.md b/documents/vol4-advanced/08-templates-and-inheritance-crtp.md deleted file mode 100644 index 5ddb51e7c..000000000 --- a/documents/vol4-advanced/08-templates-and-inheritance-crtp.md +++ /dev/null @@ -1,2142 +0,0 @@ ---- -chapter: 12 -cpp_standard: -- 11 -- 14 -- 17 -- 20 -description: 深入理解奇异递归模板模式、静态多态与Mixin模式 -difficulty: intermediate -order: 8 -platform: host -prerequisites: -- 'Chapter 12: 模板入门概述' -- 'Chapter 12: 函数模板详解' -- 'Chapter 2: CRTP vs 运行时多态' -reading_time_minutes: 49 -tags: -- cpp-modern -- host -- intermediate -title: 模板与继承:CRTP与静态多态 ---- -# 嵌入式现代C++教程——模板与继承:CRTP与静态多态 - -## 引言:当模板遇见继承 - -模板和继承是C++两大核心特性,但它们通常被视为彼此独立的工具——模板用于泛型编程,继承用于面向对象设计。然而,当这两者结合时,会产生一些强大而优雅的模式。 - -其中最著名的就是**奇异递归模板模式(Curiously Recurring Template Pattern,CRTP)**。这个名字听起来很奇怪,但它的应用场景非常广泛:从单例模式到对象计数,从多态复制到接口注入。 - -本章我们将深入探讨CRTP的原理与应用,对比静态多态与动态多态的优劣,并学习如何使用Mixin模式为类添加功能。 - ------- - -## CRTP:奇异递归模板模式 - -### 什么是CRTP? - -CRTP是一种C++惯用法,其核心思想是:**派生类将自己作为模板参数传递给基类**。 - -```cpp -template -class Base { -public: - void interface() { - // 基类通过转型调用派生类的实现 - static_cast(this)->implementation(); - } -}; - -class Derived : public Base { -public: - void implementation() { - // 派生类的具体实现 - } -}; -``` - -这个模式看起来"奇异"是因为: - -1. 派生类继承自一个以自己为模板参数的基类 -2. 基类通过`static_cast(this)`访问派生类的成员 - -### 为什么需要CRTP? - -CRTP解决了三个核心问题: - -1. **静态多态**:在不使用虚函数的情况下实现多态行为 -2. **代码复用**:在基类中实现通用逻辑,调用派生类的具体实现 -3. **编译期类型检查**:确保派生类实现了所需的接口 - -让我们通过一个嵌入式场景来理解——设备驱动框架: - -```cpp -// 设备基类(CRTP) -template -class DeviceBase { -public: - // 通用初始化流程 - void initialize() { - // 1. 硬件复位 - static_cast(this)->reset_hardware(); - - // 2. 配置寄存器 - static_cast(this)->configure_registers(); - - // 3. 校准 - static_cast(this)->calibrate(); - - // 4. 通用后处理 - static_cast(this)->set_initialized(); - } - - // 通用读取流程 - auto read() { - static_cast(this)->start_conversion(); - while (!static_cast(this)->is_ready()) { - // 等待转换完成 - } - return static_cast(this)->read_value(); - } -}; - -// ADC设备实现 -class ADCDevice : public DeviceBase { -public: - void reset_hardware() { - // ADC特定的复位逻辑 - } - - void configure_registers() { - // ADC特定的配置 - } - - void calibrate() { - // ADC特定的校准 - } - - void set_initialized() { - // 标记初始化完成 - } - - void start_conversion() { - // 启动ADC转换 - } - - bool is_ready() { - // 检查转换是否完成 - return true; - } - - uint16_t read_value() { - // 读取ADC值 - return 0; - } -}; - -// 使用 -ADCDevice adc; -adc.initialize(); -uint16_t value = adc.read(); -``` - -**关键点**: - -- 所有设备共享相同的初始化和读取流程 -- 每个设备提供自己的具体实现 -- 没有虚函数调用,所有调用都可以内联 -- 编译期保证类型安全 - -### CRTP的本质:静态多态 - -CRTP实现的是**静态多态**(编译期多态),与虚函数实现的**动态多态**(运行时多态)形成对比: - -| 特性 | 动态多态(虚函数) | 静态多态(CRTP) | -|------|-------------------|-----------------| -| 绑定时机 | 运行时 | 编译期 | -| 性能开销 | 虚表查找 + 间接调用 | 零开销(可内联) | -| 内存开销 | 每个对象一个vptr | 无额外内存 | -| 类型检查 | 运行时(通过虚表) | 编译期 | -| 代码大小 | 较小(一份函数实现) | 较大(每个类型一份) | -| 二进制兼容性 | 稳定(ABI兼容) | 不稳定(模板实例化) | -| 可扩展性 | 运行时可添加新类型 | 编译期确定 | - ------- - -## CRTP的工作原理 - -### 类型转换详解 - -CRTP的核心在于`static_cast(this)`: - -```cpp -template -class Base { -public: - void method() { - Derived* d = static_cast(this); - d->impl(); // 调用派生类方法 - } -}; -``` - -**为什么这样是安全的?** - -当`Derived`继承自`Base`时: - -1. `Base`的`this`指针实际上指向`Derived`对象 -2. `static_cast`不会改变指针值,只是改变编译器的类型理解 -3. 这类似于`void*`到具体类型的转换,但更安全 - -**布局保证**: - -```cpp -class Derived : public Base { - int data; -}; - -// 内存布局: -// [Base部分] [Derived部分] -// ↑ ↑ -// this 派生类数据 -``` - -### 编译期类型检查 - -CRTP在编译期检查派生类是否实现了所需接口: - -```cpp -template -class Base { -public: - void interface() { - // 如果Derived没有实现implementation(),编译失败 - static_cast(this)->implementation(); - } -}; - -class Derived : public Base { - // 未实现implementation() -}; - -// 编译错误:'class Derived' has no member named 'implementation' -``` - -这种检查发生在实例化时,而非模板定义时。 - -### 完整示例:多态复制 - -CRTP的经典应用是实现多态的`clone()`方法: - -```cpp -template -class Cloneable { -public: - // 克隆接口,返回正确的派生类类型 - [[nodiscard]] Derived* clone() const { - return new Derived(static_cast(*this)); - } - - [[nodiscard]] std::unique_ptr unique_clone() const { - return std::make_unique(static_cast(*this)); - } -}; - -class Sensor : public Cloneable { -public: - Sensor(const Sensor& other) = default; - // ... -}; - -class TemperatureSensor : public Cloneable { -public: - TemperatureSensor(const TemperatureSensor& other) = default; - // ... -}; - -// 使用 -TemperatureSensor ts1; -auto ts2 = ts1.unique_clone(); // 返回unique_ptr -``` - -对比虚函数版本: - -```cpp -// 虚函数版本 -class Sensor { -public: - virtual Sensor* clone() const = 0; - virtual ~Sensor() = default; -}; - -class TemperatureSensor : public Sensor { -public: - TemperatureSensor* clone() const override { - return new TemperatureSensor(*this); - } -}; - -// 使用 -TemperatureSensor ts1; -auto ts2 = std::unique_ptr(ts1.clone()); // 返回unique_ptr,丢失了具体类型 -``` - -CRTP版本的优势:**返回类型是具体的派生类类型,不需要额外的类型转换**。 - ------- - -## CRTP实战:单例基类 - -### 问题分析 - -单例模式是最常用的设计模式之一,但每个单例类都需要重复编写相同的代码: - -```cpp -class MySingleton { -public: - MySingleton(const MySingleton&) = delete; - MySingleton& operator=(const MySingleton&) = delete; - - static MySingleton& instance() { - static MySingleton inst; - return inst; - } - -private: - MySingleton() = default; -}; -``` - -### CRTP解决方案 - -使用CRTP可以实现一个通用的单例基类: - -```cpp -template -class Singleton { -public: - Singleton(const Singleton&) = delete; - Singleton& operator=(const Singleton&) = delete; - - static Derived& instance() { - // C++11保证局部静态变量的线程安全初始化 - static Derived inst; - return inst; - } - -protected: - Singleton() = default; - ~Singleton() = default; -}; -``` - -### 完整实现 - -```cpp -#include - -template -class Singleton { -public: - // 禁止拷贝和移动 - Singleton(const Singleton&) = delete; - Singleton& operator=(const Singleton&) = delete; - Singleton(Singleton&&) = delete; - Singleton& operator=(Singleton&&) = delete; - - // 获取单例引用 - [[nodiscard]] static Derived& instance() { - static Derived inst; - return inst; - } - - // 获取单例指针(可选,用于更简洁的访问) - [[nodiscard]] static Derived* ptr() { - return &instance(); - } - -protected: - Singleton() = default; - virtual ~Singleton() = default; -}; - -// 使用示例 -class Logger : public Singleton { - // 让基类可以访问构造函数 - friend class Singleton; - -public: - void log(const char* msg) { - // 日志实现 - } - -private: - Logger() { - // 初始化日志系统 - } - - ~Logger() override { - // 清理资源 - } -}; - -// 使用 -int main() { - Logger::instance().log("System starting"); - Logger::ptr()->log("Another message"); - return 0; -} -``` - -### 嵌入式版本(Meyer's Singleton) - -在嵌入式系统中,我们可能需要更精细的控制: - -```cpp -template -class EmbeddedSingleton { -public: - EmbeddedSingleton(const EmbeddedSingleton&) = delete; - EmbeddedSingleton& operator=(const EmbeddedSingleton&) = delete; - - static Derived& instance() { - std::call_once(init_flag_, &EmbeddedSingleton::init); - return *instance_; - } - - // 手动初始化(用于控制初始化时机) - static void init() { - if (!instance_) { - instance_ = new Derived(); - } - } - - // 手动销毁(用于控制销毁时机) - static void destroy() { - delete instance_; - instance_ = nullptr; - } - -protected: - EmbeddedSingleton() = default; - virtual ~EmbeddedSingleton() { - instance_ = nullptr; - } - -private: - static Derived* instance_; - static std::once_flag init_flag_; - static Mutex mutex_; -}; - -template -Derived* EmbeddedSingleton::instance_ = nullptr; - -template -std::once_flag EmbeddedSingleton::init_flag_; - -template -Mutex EmbeddedSingleton::mutex_; -``` - -### 线程安全保证 - -C++11之后,局部静态变量的初始化是线程安全的: - -```cpp -static Derived inst; // 编译器保证线程安全的初始化 -``` - -但如果你需要更早的C++标准支持或更细粒度的控制,可以使用`std::call_once`: - -```cpp -template -class ThreadSafeSingleton { -public: - static Derived& instance() { - std::call_once(init_flag_, []() { - instance_ = new Derived(); - }); - return *instance_; - } - -private: - static Derived* instance_; - static std::once_flag init_flag_; -}; - -template -Derived* ThreadSafeSingleton::instance_ = nullptr; - -template -std::once_flag ThreadSafeSingleton::init_flag_; -``` - -### 实际应用:设备管理器 - -```cpp -class DeviceManager : public Singleton { - friend class Singleton; - -public: - void register_device(const char* name, void* device) { - devices_[device_count_] = {name, device}; - device_count_++; - } - - void* get_device(const char* name) { - for (size_t i = 0; i < device_count_; ++i) { - if (std::strcmp(devices_[i].name, name) == 0) { - return devices_[i].device; - } - } - return nullptr; - } - -private: - struct DeviceEntry { - const char* name; - void* device; - }; - - DeviceEntry devices_[16]; - size_t device_count_ = 0; - - DeviceManager() = default; -}; -``` - -::: details 查看完整单例实现示例 - -```cpp -#include -#include - -template -class Singleton { -public: - Singleton(const Singleton&) = delete; - Singleton& operator=(const Singleton&) = delete; - Singleton(Singleton&&) = delete; - Singleton& operator=(Singleton&&) = delete; - - [[nodiscard]] static Derived& instance() { - static Derived inst; - return inst; - } - - [[nodiscard]] static Derived* ptr() { - return &instance(); - } - -protected: - Singleton() = default; - virtual ~Singleton() = default; -}; - -class DeviceManager : public Singleton { - friend class Singleton; - -public: - bool register_device(const char* name, void* device) { - if (device_count_ >= max_devices_) { - return false; - } - devices_[device_count_] = {name, device}; - device_count_++; - return true; - } - - void* get_device(const char* name) const { - for (size_t i = 0; i < device_count_; ++i) { - if (std::strcmp(devices_[i].name, name) == 0) { - return devices_[i].device; - } - } - return nullptr; - } - - size_t device_count() const { - return device_count_; - } - -private: - struct DeviceEntry { - const char* name; - void* device; - }; - - static constexpr size_t max_devices_ = 32; - DeviceEntry devices_[max_devices_]; - size_t device_count_ = 0; - - DeviceManager() = default; - ~DeviceManager() override = default; -}; - -// 使用示例 -int main() { - auto& dm = DeviceManager::instance(); - - int uart1 = 1; - int spi1 = 2; - int i2c1 = 3; - - dm.register_device("UART1", &uart1); - dm.register_device("SPI1", &spi1); - dm.register_device("I2C1", &i2c1); - - void* dev = dm.get_device("UART1"); - return 0; -} - -``` - -::: - ------- - -## CRTP实战:对象计数器 - -### 应用场景 - -在嵌入式系统中,我们经常需要: - -- 跟踪某个类创建了多少个对象 -- 检测内存泄漏 -- 监控资源使用情况 -- 实现对象池 - -### 基础实现 - -```cpp - -template -class ObjectCounter { -public: - static size_t get_count() { - return count_; - } - -protected: - ObjectCounter() { - ++count_; - } - - ObjectCounter(const ObjectCounter&) { - ++count_; - } - - ObjectCounter(ObjectCounter&&) { - ++count_; - } - - ~ObjectCounter() { - --count_; - } - -private: - static size_t count_; -}; - -template -size_t ObjectCounter::count_ = 0; -``` - -### 使用示例 - -```cpp -class Sensor : public ObjectCounter { -public: - Sensor() = default; - // ... -}; - -void test_sensor_counting() { - printf("Initial: %zu sensors\n", Sensor::get_count()); // 0 - - { - Sensor s1; - printf("After s1: %zu sensors\n", Sensor::get_count()); // 1 - - Sensor s2; - printf("After s2: %zu sensors\n", Sensor::get_count()); // 2 - - { - Sensor s3; - printf("After s3: %zu sensors\n", Sensor::get_count()); // 3 - } - printf("After s3 destroyed: %zu sensors\n", Sensor::get_count()); // 2 - } - printf("After all destroyed: %zu sensors\n", Sensor::get_count()); // 0 -} -``` - -### 进阶:移动和拷贝计数 - -```cpp -template -class DetailedObjectCounter { -public: - static size_t get_alive_count() { - return alive_count_; - } - - static size_t get_total_created() { - return total_created_; - } - - static size_t get_total_copied() { - return copy_count_; - } - - static size_t get_total_moved() { - return move_count_; - } - - static void reset_stats() { - alive_count_ = 0; - total_created_ = 0; - copy_count_ = 0; - move_count_ = 0; - } - -protected: - DetailedObjectCounter() noexcept { - ++alive_count_; - ++total_created_; - } - - ~DetailedObjectCounter() { - --alive_count_; - } - - DetailedObjectCounter(const DetailedObjectCounter&) noexcept { - ++alive_count_; - ++total_created_; - ++copy_count_; - } - - DetailedObjectCounter(DetailedObjectCounter&&) noexcept { - ++alive_count_; - ++total_created_; - ++move_count_; - } - - DetailedObjectCounter& operator=(const DetailedObjectCounter&) = default; - DetailedObjectCounter& operator=(DetailedObjectCounter&&) = default; - -private: - static size_t alive_count_; - static size_t total_created_; - static size_t copy_count_; - static size_t move_count_; -}; - -template -size_t DetailedObjectCounter::alive_count_ = 0; - -template -size_t DetailedObjectCounter::total_created_ = 0; - -template -size_t DetailedObjectCounter::copy_count_ = 0; - -template -size_t DetailedObjectCounter::move_count_ = 0; -``` - -### 内存泄漏检测 - -```cpp -template -class LeakDetector : public ObjectCounter { -public: - ~LeakDetector() { - if (ObjectCounter::get_count() > 0) { - // 在实际系统中,这里可能记录日志或触发警告 - printf("Warning: %zu instances of %s still alive!\n", - ObjectCounter::get_count(), - Derived::class_name()); - } - } -}; - -class Buffer : public LeakDetector { -public: - static const char* class_name() { - return "Buffer"; - } - - Buffer(size_t size) : data_(new uint8_t[size]), size_(size) {} - - ~Buffer() { - delete[] data_; - } - -private: - uint8_t* data_; - size_t size_; -}; -``` - -### 资源监控 - -```cpp -template -class BoundedCounter : public ObjectCounter { -public: - static bool can_create() { - return ObjectCounter::get_count() < MaxInstances; - } - - static size_t remaining_capacity() { - return MaxInstances - ObjectCounter::get_count(); - } - -protected: - BoundedCounter() { - if (!can_create()) { - throw std::runtime_error("Maximum instances reached"); - } - } -}; - -// 使用:限制最多创建8个传感器 -class LimitedSensor : public BoundedCounter { -public: - LimitedSensor() = default; -}; -``` - -::: details 查看完整对象计数器实现 - -```cpp -#include -#include -#include - -template -class ObjectCounter { -public: - static size_t get_count() { - return count_; - } - -protected: - ObjectCounter() { - ++count_; - } - - ObjectCounter(const ObjectCounter&) { - ++count_; - } - - ObjectCounter(ObjectCounter&&) { - ++count_; - } - - ~ObjectCounter() { - --count_; - } - -private: - static size_t count_; -}; - -template -size_t ObjectCounter::count_ = 0; - -// 检测泄漏的版本 -template -class LeakDetector : public ObjectCounter { -public: - ~LeakDetector() { - if (ObjectCounter::get_count() > 0) { - printf("[LeakDetector] Warning: %zu instances of %s leaked!\n", - ObjectCounter::get_count(), - Derived::static_class_name()); - } - } -}; - -// 示例类 -class Sensor : public LeakDetector { -public: - static const char* static_class_name() { - return "Sensor"; - } - - Sensor(int id) : id_(id) { - printf("[Sensor] Sensor %d created. Total: %zu\n", - id_, get_count()); - } - - ~Sensor() { - printf("[Sensor] Sensor %d destroyed. Remaining: %zu\n", - id_, get_count()); - } - -private: - int id_; -}; - -// 限制数量的版本 -template -class BoundedCounter : public ObjectCounter { -public: - static constexpr size_t max_instances = MaxInstances; - static size_t remaining() { - return MaxInstances - ObjectCounter::get_count(); - } - -protected: - BoundedCounter() { - if (!can_create()) { - throw std::runtime_error("Maximum instances exceeded"); - } - } - -private: - static bool can_create() { - return ObjectCounter::get_count() < MaxInstances; - } -}; - -class LimitedBuffer : public BoundedCounter { -public: - LimitedBuffer(size_t size) : size_(size) { - printf("[LimitedBuffer] Created %zu-byte buffer. Remaining capacity: %zu\n", - size_, remaining()); - } - - ~LimitedBuffer() { - printf("[LimitedBuffer] Destroyed buffer. Remaining: %zu\n", remaining()); - } - -private: - size_t size_; -}; - -int main() { - printf("=== Object Counter Demo ===\n"); - - { - Sensor s1(1); - Sensor s2(2); - { - Sensor s3(3); - } - Sensor s4(4); - } - - printf("\n=== Bounded Buffer Demo ===\n"); - - try { - LimitedBuffer b1(1024); - LimitedBuffer b2(2048); - LimitedBuffer b3(4096); - LimitedBuffer b4(8192); - printf("Created 4 buffers successfully\n"); - - LimitedBuffer b5(16384); // 应该抛出异常 - } catch (const std::exception& e) { - printf("Exception: %s\n", e.what()); - } - - return 0; -} - -``` - -::: - ------- - -## Mixin模式 - -### 什么是Mixin? - -Mixin是一种通过继承来组合功能的模式,它允许你将可复用的功能"混入"到类中。CRTP是实现Mixin的完美工具。 - -### 基本Mixin - -```cpp - -// Printable Mixin:为类添加打印功能 -template -class Printable { -public: - void print() const { - const Derived* d = static_cast(this); - d->print_to(std::cout); - } - - void print_to(std::ostream& os) const { - const Derived* d = static_cast(this); - d->print_to(os); - } -}; - -// Comparable Mixin:为类添加比较功能 -template -class Comparable { -public: - bool operator<(const Comparable& other) const { - const Derived* d = static_cast(this); - const Derived* o = static_cast(&other); - return d->compare(*o) < 0; - } - - bool operator==(const Comparable& other) const { - const Derived* d = static_cast(this); - const Derived* o = static_cast(&other); - return d->compare(*o) == 0; - } - - bool operator!=(const Comparable& other) const { - return !(*this == other); - } - - bool operator<=(const Comparable& other) const { - return !(other < *this); - } - - bool operator>(const Comparable& other) const { - return other < *this; - } - - bool operator>=(const Comparable& other) const { - return !(*this < other); - } -}; -``` - -### 多Mixin组合 - -一个类可以继承多个Mixin: - -```cpp -class Sensor : public Printable, - public Comparable, - public ObjectCounter { -public: - Sensor(int id, int value) : id_(id), value_(value) {} - - void print_to(std::ostream& os) const { - os << "Sensor{id=" << id_ << ", value=" << value_ << "}"; - } - - int compare(const Sensor& other) const { - if (id_ != other.id_) { - return id_ - other.id_; - } - return value_ - other.value_; - } - -private: - int id_; - int value_; -}; - -// 使用 -void demo_mixins() { - Sensor s1(1, 100); - Sensor s2(2, 200); - - s1.print(); // 来自Printable - if (s1 < s2) { // 来自Comparable - printf("s1 < s2\n"); - } - - printf("Total sensors: %zu\n", Sensor::get_count()); // 来自ObjectCounter -} -``` - -### 嵌入式应用:状态跟踪Mixin - -```cpp -template -class StateTracking { -public: - enum class State { - Uninitialized, - Initializing, - Ready, - Running, - Error, - Suspended - }; - - State get_state() const { - return state_; - } - - const char* get_state_name() const { - switch (state_) { - case State::Uninitialized: return "Uninitialized"; - case State::Initializing: return "Initializing"; - case State::Ready: return "Ready"; - case State::Running: return "Running"; - case State::Error: return "Error"; - case State::Suspended: return "Suspended"; - default: return "Unknown"; - } - } - - bool is_ready() const { - return state_ == State::Ready; - } - - bool is_running() const { - return state_ == State::Running; - } - - bool has_error() const { - return state_ == State::Error; - } - -protected: - void set_state(State new_state) { - if (state_ != new_state) { - State old_state = state_; - state_ = new_state; - on_state_changed(old_state, new_state); - } - } - - virtual void on_state_changed(State old_state, State new_state) { - // 默认空实现,派生类可以重写 - (void)old_state; - (void)new_state; - } - -private: - State state_ = State::Uninitialized; -}; - -// 使用 -class Motor : public StateTracking { -public: - void initialize() { - set_state(State::Initializing); - // 初始化逻辑 - set_state(State::Ready); - } - - void start() { - if (is_ready()) { - set_state(State::Running); - } - } - - void stop() { - set_state(State::Ready); - } - - void on_state_changed(State old_state, State new_state) override { - printf("Motor state: %s -> %s\n", - state_to_string(old_state), - state_to_string(new_state)); - } - -private: - const char* state_to_string(State s) { - switch (s) { - case State::Initializing: return "Initializing"; - case State::Ready: return "Ready"; - case State::Running: return "Running"; - default: return "Other"; - } - } -}; -``` - -### 线程安全Mixin - -```cpp -template -class ThreadSafe { -protected: - using Lock = std::lock_guard; - - Mutex& mutex() { - return mutex_; - } - - const Mutex& mutex() const { - return mutex_; - } - -private: - mutable Mutex mutex_; -}; - -// 使用 -class ThreadSafeCounter : public ThreadSafe { -public: - void increment() { - Lock lock(mutex()); - ++value_; - } - - int get() const { - Lock lock(mutex()); - return value_; - } - -private: - int value_ = 0; -}; -``` - -### 配置管理Mixin - -```cpp -template -class Configurable { -public: - template - void set(const char* key, const T& value) { - config_[key] = ConfigValue(value); - } - - template - T get(const char* key, const T& default_value) const { - auto it = config_.find(key); - if (it != config_.end()) { - return std::any_cast(it->second); - } - return default_value; - } - - bool has(const char* key) const { - return config_.find(key) != config_.end(); - } - -protected: - using ConfigValue = std::any; - using ConfigMap = std::unordered_map; - - ConfigMap config_; -}; - -// 使用 -class ConfigurableSensor : public Configurable { -public: - void apply_config() { - int sample_rate = get("sample_rate", 1000); - bool enabled = get("enabled", true); - // 应用配置 - } -}; -``` - -::: details 查看完整Mixin示例 - -```cpp -#include -#include -#include -#include -#include - -// Printable Mixin -template -class Printable { -public: - void print() const { - static_cast(this)->print_to(std::cout); - } - - void print_to(std::ostream& os) const { - static_cast(this)->print_to(os); - } -}; - -// Observable Mixin:观察者模式 -template -class Observable { -public: - using Callback = std::function; - - void subscribe(Callback callback) { - callbacks_.push_back(std::move(callback)); - } - - void notify() const { - const Derived& d = *static_cast(this); - for (const auto& cb : callbacks_) { - cb(d); - } - } - -protected: - ~Observable() = default; - -private: - std::vector callbacks_; -}; - -// 验证Mixin -template -class Validatable { -public: - bool is_valid() const { - return static_cast(this)->validate(); - } - - explicit operator bool() const { - return is_valid(); - } -}; - -// 组合多个Mixin的示例类 -class TemperatureSensor : public Printable, - public Observable, - public Validatable { -public: - TemperatureSensor(float min, float max) - : min_temp_(min), max_temp_(max), current_temp_(0.0f) {} - - void set_temperature(float temp) { - current_temp_ = temp; - if (is_valid()) { - notify(); - } - } - - float get_temperature() const { - return current_temp_; - } - - // Printable实现 - void print_to(std::ostream& os) const { - os << "TemperatureSensor{temp=" << current_temp_ - << ", range=[" << min_temp_ << "," << max_temp_ << "]}"; - } - - // Validatable实现 - bool validate() const { - return current_temp_ >= min_temp_ && current_temp_ <= max_temp_; - } - -private: - float min_temp_; - float max_temp_; - float current_temp_; -}; - -int main() { - TemperatureSensor sensor(-40.0f, 125.0f); - - // 订阅温度变化 - sensor.subscribe([](const TemperatureSensor& s) { - std::cout << "Temperature changed: "; - s.print(); - std::cout << std::endl; - }); - - // 设置有效温度 - sensor.set_temperature(25.0f); - sensor.print(); // TemperatureSensor{temp=25, range=[-40,125]} - - std::cout << "Valid: " << (sensor ? "yes" : "no") << std::endl; - - // 设置无效温度(超出范围) - sensor.set_temperature(150.0f); - std::cout << "Valid: " << (sensor ? "yes" : "no") << std::endl; - - return 0; -} - -``` - -::: - ------- - -## 性能分析:CRTP vs 虚函数 - -### 测试场景 - -让我们通过一个实际的测试来比较CRTP和虚函数的性能差异: - -```cpp - -// 虚函数版本 -class ShapeVirtual { -public: - virtual ~ShapeVirtual() = default; - virtual double area() const = 0; -}; - -class CircleVirtual : public ShapeVirtual { -public: - CircleVirtual(double r) : radius_(r) {} - double area() const override { - return 3.14159 * radius_ * radius_; - } -private: - double radius_; -}; - -// CRTP版本 -template -class ShapeCRTP { -public: - double area() const { - return static_cast(this)->area_impl(); - } -}; - -class CircleCRTP : public ShapeCRTP { -public: - CircleCRTP(double r) : radius_(r) {} - double area_impl() const { - return 3.14159 * radius_ * radius_; - } -private: - double radius_; -}; -``` - -### 汇编代码对比 - -使用`-O2`优化级别的汇编输出(ARM GCC): - -**虚函数版本调用**: - -```asm -; vtable查找 + 间接调用 -ldr r0, [r0] ; 加载对象指针 -ldr r0, [r0, #4] ; 加载vtable指针 -ldr r0, [r0, #8] ; 从vtable加载area()函数指针 -bx r0 ; 间接跳转 -``` - -**CRTP版本调用**: - -```asm -; 直接调用(可能内联) -; 当类型已知时,编译器直接内联计算 -vmul.f64 d0, d0, d0 ; r * r -vmul.f64 d0, d0, d1 ; * pi -``` - -### 性能测试结果 - -在STM32F4(180MHz ARM Cortex-M4)上的测试结果: - -| 测试场景 | 虚函数 (ns) | CRTP (ns) | 提升 | -|---------|------------|-----------|------| -| 单次调用 | 45 | 12 | 3.75x | -| 循环1000次 | 42000 | 11000 | 3.82x | -| 循环内联后 | 42000 | 3000 | 14x | - -**关键发现**: - -1. CRTP在简单调用上有3-4倍性能优势 -2. 当编译器能够完全内联时,优势扩大到14倍 -3. 虚函数的间接调用会阻碍内联优化 - -### 完整基准测试代码 - -```cpp -#include -#include -#include -#include - -// ... 上面的类定义 ... - -constexpr size_t iterations = 1000000; - -double benchmark_virtual(const std::vector& shapes) { - auto start = std::chrono::high_resolution_clock::now(); - - double sum = 0; - for (size_t i = 0; i < iterations; ++i) { - for (auto* shape : shapes) { - sum += shape->area(); - } - } - - auto end = std::chrono::high_resolution_clock::now(); - auto duration = std::chrono::duration_cast(end - start); - - std::cout << "Virtual sum: " << sum << ", time: " << duration.count() << " us\n"; - return duration.count(); -} - -template -double benchmark_crtp(CircleCRTP (&circles)[N]) { - auto start = std::chrono::high_resolution_clock::now(); - - double sum = 0; - for (size_t i = 0; i < iterations; ++i) { - for (auto& circle : circles) { - sum += circle.area(); - } - } - - auto end = std::chrono::high_resolution_clock::now(); - auto duration = std::chrono::duration_cast(end - start); - - std::cout << "CRTP sum: " << sum << ", time: " << duration.count() << " us\n"; - return duration.count(); -} - -int main() { - constexpr size_t num_shapes = 10; - - // 虚函数版本 - std::vector vshapes; - std::vector vcircles; - vcircles.reserve(num_shapes); - for (size_t i = 0; i < num_shapes; ++i) { - vcircles.emplace_back(1.0 + i * 0.1); - vshapes.push_back(&vcircles.back()); - } - - // CRTP版本 - CircleCRTP ccircles[num_shapes]; - for (size_t i = 0; i < num_shapes; ++i) { - ccircles[i] = CircleCRTP(1.0 + i * 0.1); - } - - // 运行基准测试 - double vtime = benchmark_virtual(vshapes); - double ctime = benchmark_crtp(ccircles); - - std::cout << "Speedup: " << (vtime / ctime) << "x\n"; - - return 0; -} -``` - -### 内存占用对比 - -| 方面 | 虚函数 | CRTP | -|------|--------|------| -| 每个对象额外开销 | 1个指针(vptr,4-8字节) | 0字节 | -| vtable存储 | 每个类1个vtable(Flash) | 无 | -| 代码大小 | 较小(函数共享) | 较大(每个类型一份) | -| 内联可能性 | 低(间接调用) | 高(直接调用) | - -### 何时选择CRTP vs 虚函数 - -**选择CRTP的场景**: - -- 性能关键代码(ISR、实时循环) -- 类型在编译期确定 -- 需要内联优化 -- RAM紧张(避免vptr开销) -- 不需要运行时动态替换实现 - -**选择虚函数的场景**: - -- 需要运行时多态(插件系统) -- 类型在编译期不确定 -- 通过接口访问对象 -- 需要ABI稳定性 -- 代码大小比性能更重要 - -::: details 查看完整性能测试代码 - -```cpp -#include -#include -#include -#include -#include - -// 虚函数版本 -class ShapeVirtual { -public: - virtual ~ShapeVirtual() = default; - virtual double area() const = 0; - virtual double perimeter() const = 0; -}; - -class RectangleVirtual : public ShapeVirtual { -public: - RectangleVirtual(double w, double h) : width_(w), height_(h) {} - - double area() const override { - return width_ * height_; - } - - double perimeter() const override { - return 2 * (width_ + height_); - } - -private: - double width_; - double height_; -}; - -class CircleVirtual : public ShapeVirtual { -public: - explicit CircleVirtual(double r) : radius_(r) {} - - double area() const override { - return 3.14159265359 * radius_ * radius_; - } - - double perimeter() const override { - return 2 * 3.14159265359 * radius_; - } - -private: - double radius_; -}; - -// CRTP版本 -template -class ShapeCRTP { -public: - double area() const { - return static_cast(this)->area_impl(); - } - - double perimeter() const { - return static_cast(this)->perimeter_impl(); - } -}; - -class RectangleCRTP : public ShapeCRTP { -public: - RectangleCRTP(double w, double h) : width_(w), height_(h) {} - - double area_impl() const { - return width_ * height_; - } - - double perimeter_impl() const { - return 2 * (width_ + height_); - } - -private: - double width_; - double height_; -}; - -class CircleCRTP : public ShapeCRTP { -public: - explicit CircleCRTP(double r) : radius_(r) {} - - double area_impl() const { - return 3.14159265359 * radius_ * radius_; - } - - double perimeter_impl() const { - return 2 * 3.14159265359 * radius_; - } - -private: - double radius_; -}; - -// 基准测试工具 -class Benchmark { -public: - static constexpr size_t iterations = 10000000; - - template - static double run(const char* name, Func&& func) { - // 预热 - for (int i = 0; i < 1000; ++i) { - func(); - } - - auto start = std::chrono::high_resolution_clock::now(); - - volatile double result = 0; // volatile防止优化 - for (size_t i = 0; i < iterations; ++i) { - result += func(); - } - - auto end = std::chrono::high_resolution_clock::now(); - auto duration = std::chrono::duration_cast(end - start); - - double avg_ns = static_cast(duration.count()) / iterations; - - std::cout << std::left << std::setw(30) << name - << " Avg: " << std::setw(8) << std::fixed << std::setprecision(2) << avg_ns << " ns" - << " Total: " << std::setw(8) << duration.count() / 1000000 << " ms" - << " Result: " << result << "\n"; - - return avg_ns; - } -}; - -// 测试多态容器 -void test_polymorphic_container() { - std::cout << "\n=== Polymorphic Container Test ===\n"; - - std::vector> vshapes; - vshapes.push_back(std::make_unique(1.0)); - vshapes.push_back(std::make_unique(2.0, 3.0)); - vshapes.push_back(std::make_unique(2.5)); - - double sum = 0; - auto start = std::chrono::high_resolution_clock::now(); - - for (size_t i = 0; i < Benchmark::iterations; ++i) { - for (const auto& shape : vshapes) { - sum += shape->area(); - } - } - - auto end = std::chrono::high_resolution_clock::now(); - auto duration = std::chrono::duration_cast(end - start); - - std::cout << "Virtual polymorphic: " << duration.count() << " ms\n"; - - // CRTP无法在运行时多态容器中使用 - // 这是虚函数的一个优势 -} - -// 单类型性能测试 -void test_single_type_performance() { - std::cout << "\n=== Single Type Performance Test ===\n"; - - constexpr double radius = 2.5; - - // 虚函数版本 - CircleVirtual vcircle(radius); - - double vtime = Benchmark::run("Virtual (single type)", [&]() { - return vcircle.area(); - }); - - // CRTP版本 - CircleCRTP ccircle(radius); - - double ctime = Benchmark::run("CRTP (single type)", [&]() { - return ccircle.area(); - }); - - std::cout << "\nSpeedup: " << (vtime / ctime) << "x\n"; -} - -// 内联测试 -void test_inlining() { - std::cout << "\n=== Inlining Test ===\n"; - - constexpr double radius = 1.5; - CircleCRTP circle(radius); - - // 直接计算(基准) - double baseline = Benchmark::run("Direct calculation", [&]() { - return 3.14159265359 * radius * radius; - }); - - // CRTP(应该内联到与直接计算相同) - double crtp = Benchmark::run("CRTP (should inline)", [&]() { - return circle.area(); - }); - - std::cout << "\nCRTP overhead vs direct: " << ((crtp / baseline) - 1.0) * 100 << "%\n"; -} - -int main() { - std::cout << "CRTP vs Virtual Performance Benchmark\n"; - std::cout << "Iterations: " << Benchmark::iterations << "\n\n"; - - test_single_type_performance(); - test_inlining(); - test_polymorphic_container(); - - std::cout << "\n=== Memory Usage ===\n"; - std::cout << "sizeof(CircleVirtual): " << sizeof(CircleVirtual) << " bytes (includes vptr)\n"; - std::cout << "sizeof(CircleCRTP): " << sizeof(CircleCRTP) << " bytes (no vptr)\n"; - std::cout << "sizeof(RectangleVirtual): " << sizeof(RectangleVirtual) << " bytes\n"; - std::cout << "sizeof(RectangleCRTP): " << sizeof(RectangleCRTP) << " bytes\n"; - - return 0; -} - -``` - -::: - ------- - -## CRTP进阶技巧 - -### 1. 完美转发到派生类 - -```cpp - -template -class Base { -public: - template - auto construct(Args&&... args) { - return static_cast(this)->construct_impl( - std::forward(args)...); - } -}; - -class Derived : public Base { -public: - template - T construct_impl(T&& arg) { - return T{std::forward(arg)}; - } -}; -``` - -### 2. CRTP与decltype(auto) - -```cpp -template -class Base { -public: - decltype(auto) get_value() { - return static_cast(this)->get_value_impl(); - } -}; - -class Derived : public Base { -public: - int& get_value_impl() { - return value_; - } - -private: - int value_; -}; - -// get_value()返回int&,保留引用语义 -``` - -### 3. 约束派生类接口 - -使用C++20 Concepts可以约束CRTP接口: - -```cpp -template -concept CRTPDerived = requires(Derived d) { - { d.interface() } -> std::same_as; -}; - -template -class Base { -public: - void wrapper() { - static_cast(this)->interface(); - } -}; -``` - -### 4. CRTP与类型萃取 - -```cpp -template -class Base { -public: - using derived_type = Derived; - - Derived& derived() { - return static_cast(*this); - } - - const Derived& derived() const { - return static_cast(*this); - } -}; - -// 使用 -class Derived : public Base { -public: - void method() { - // 可以使用derived()代替static_cast - derived().implementation(); - } -}; -``` - -### 5. 多参数CRTP - -```cpp -template -class MultiMixinBase : public Mixins... { -public: - void dispatch() { - // 调用所有Mixin的方法 - (static_cast(this)->handle(), ...); - } -}; - -template -class Logger { -public: - void handle() { - std::cout << "Logging\n"; - } -}; - -template -class Validator { -public: - void handle() { - std::cout << "Validating\n"; - } -}; - -class MyClass : public MultiMixinBase, Validator> { -}; -``` - ------- - -## 常见陷阱与解决方案 - -### 陷阱1:忘记提供派生类实现 - -```cpp -template -class Base { -public: - void interface() { - static_cast(this)->implementation(); // 编译期检查 - } -}; - -class Derived : public Base { - // 未实现implementation() -}; - -// 编译错误:没有成员'implementation' -``` - -**解决方案**:使用static_assert提供更好的错误信息: - -```cpp -template -class Base { -public: - void interface() { - static_assert(requires { static_cast(this)->implementation(); }, - "Derived must implement implementation()"); - static_cast(this)->implementation(); - } -}; -``` - -### 陷阱2:私有继承访问问题 - -```cpp -template -class Base { -public: - void method() { - // 如果Derived私有继承,可能无法访问 - static_cast(this)->impl(); - } -}; - -class Derived : private Base { // 私有继承 -public: - void impl() {} -}; -``` - -**解决方案**:使用using声明或友元: - -```cpp -template -class Base { -public: - void method() { - static_cast(this)->impl(); - } -}; - -class Derived : private Base { - friend class Base; // 声明友元 -public: - void impl() {} -}; -``` - -### 陷阱3:菱形继承 - -```cpp -template -class A {}; - -template -class B : public A {}; - -template -class C : public A {}; - -class D : public B, public C { - // A的成员重复! -}; -``` - -**解决方案**:使用虚继承: - -```cpp -template -class A {}; - -template -class B : public virtual A {}; - -template -class C : public virtual A {}; - -class D : public B, public C { - // 只有一个A基类 -}; -``` - -### 陷阱4:构造函数中调用虚函数 - -CRTP中,构造派生类时基类先构造,此时访问派生类成员是未定义行为: - -```cpp -template -class Base { -public: - Base() { - static_cast(this)->init(); // 危险! - } -}; - -class Derived : public Base { - std::vector data_; // 尚未构造 -public: - void init() { - data_.push_back(42); // 未定义行为 - } -}; -``` - -**解决方案**:提供两阶段初始化或使用工厂模式: - -```cpp -template -class Base { -public: - void initialize() { - static_cast(this)->init(); // 安全 - } -}; - -class Derived : public Base { - std::vector data_; -public: - Derived() = default; - void init() { - data_.push_back(42); // 安全 - } -}; - -// 使用 -Derived d; -d.initialize(); // 在构造完成后调用 -``` - -### 陷阱5:模板实例化顺序 - -多个CRTP类相互依赖时可能导致实例化顺序问题: - -```cpp -// A.h -template -class A { -public: - void method() { - static_cast(this)->b_method(); // B可能还未定义 - } -}; - -// B.h -template -class B { -public: - void a_method() { - static_cast(this)->method(); // 调用A - } - - void b_method() { - // B的实现 - } -}; - -// C.h -class C : public A, public B { - // 可能出现实例化顺序问题 -}; -``` - -**解决方案**:将实现放在cpp文件或使用显式实例化: - -```cpp -// 在cpp中提供实现 -template -void A::method_impl() { - static_cast(this)->b_method(); -} -``` - ------- - -## 在线运行 - -在线体验 CRTP 对象计数器、Singleton 设备管理器等经典模式: - - - -## 小结 - -CRTP是C++中一个强大而优雅的模式,它让模板和继承协同工作,实现了: - -### 核心要点 - -1. **静态多态**:在编译期实现多态,避免虚函数的开销 -2. **代码复用**:基类提供通用逻辑,派生类提供具体实现 -3. **类型安全**:编译期检查接口实现 -4. **零开销**:所有调用可以内联,性能与手写代码相同 - -### 实用模式 - -| 模式 | 用途 | 示例 | -|------|------|------| -| 单例基类 | 通用单例实现 | `Singleton` | -| 对象计数 | 跟踪对象数量、检测泄漏 | `ObjectCounter` | -| 多态复制 | 返回正确类型的clone | `Cloneable` | -| Mixin | 组合可复用功能 | `Printable`, `Comparable` | -| 接口注入 | 编译期接口检查 | `Interface` | - -### 选择建议 - -**使用CRTP当**: - -- 性能是关键因素 -- 类型在编译期确定 -- 需要内联优化 -- RAM紧张(避免vptr) - -**使用虚函数当**: - -- 需要运行时多态 -- 类型在编译期不确定 -- 需要ABI稳定性 -- 代码大小比性能更重要 - -**下一章**,我们将探讨**可变参数模板**,学习如何处理任意数量的模板参数,并实现一个类型安全的回调系统。 diff --git a/documents/vol4-advanced/cpp-templates-index.md b/documents/vol4-advanced/cpp-templates-index.md deleted file mode 100644 index 880c6eb6f..000000000 --- a/documents/vol4-advanced/cpp-templates-index.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: 现代C++模板教程(计划中) -description: '' -tags: -- cpp-modern -- host -- intermediate -difficulty: intermediate -platform: host -chapter: 12 -order: 9 ---- -# 现代C++模板教程(计划中) - -四卷系列正在规划中,敬请期待! - -- **卷一:模板基础(C++11-14)** -- **卷二:现代模板技术(C++17)** -- **卷三:元编程精要(C++20-23)** -- **卷四:泛型设计模式实战** diff --git a/documents/vol4-advanced/index.md b/documents/vol4-advanced/index.md index ad7df3550..bd9474375 100644 --- a/documents/vol4-advanced/index.md +++ b/documents/vol4-advanced/index.md @@ -18,18 +18,13 @@ tags: ## 现有文章(待重写为通用内容) -### 模板 +### 模板编程(按 C++ 标准分类) - 模板概述 - 函数模板 - 类模板 - 模板特化 - 非类型模板参数 - 模板参数与名称查找 - 模板友元与 Barton-Nackman - 模板别名与 using - 模板与继承 CRTP + 模板基础(C++11-14) + 现代模板技术(C++17) + 元编程精要(C++20-23) + 泛型设计模式实战 ### 协程 @@ -42,8 +37,6 @@ tags: ### 其他 - 现代 C++ 模板教程(计划中) - if constexpr 三路比较运算符 C++ Modules (MSVC) diff --git a/documents/vol4-advanced/vol1-basics-cpp11-14/.gitkeep b/documents/vol4-advanced/vol1-basics-cpp11-14/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/documents/vol4-advanced/vol1-basics-cpp11-14/index.md b/documents/vol4-advanced/vol1-basics-cpp11-14/index.md new file mode 100644 index 000000000..5b9117751 --- /dev/null +++ b/documents/vol4-advanced/vol1-basics-cpp11-14/index.md @@ -0,0 +1,23 @@ +--- +title: "模板基础(C++11-14)" +description: "C++模板编程的核心基础:从函数模板到 CRTP 的完整入门" +--- + +# 模板基础(C++11-14) + +> 状态:待编写 + +C++ 模板是泛型编程的核心机制。本部分从零开始建立完整的模板基础知识体系,覆盖函数模板、类模板、特化与偏特化、非类型参数、名称查找、友元注入、别名、继承与 CRTP。 + +## 本章内容(待编写) + +1. 模板导论 +2. 函数模板 +3. 类模板 +4. 模板特化与偏特化 +5. 非类型模板参数 +6. 模板参数依赖与名字查找 +7. 模板友元与 Barton-Nackman +8. 模板别名与 Using 声明 +9. 模板与继承(CRTP) +10. 综合项目:fixed_vector<T, N> diff --git a/documents/vol4-advanced/vol2-modern-cpp17/index.md b/documents/vol4-advanced/vol2-modern-cpp17/index.md new file mode 100644 index 000000000..f92100e8d --- /dev/null +++ b/documents/vol4-advanced/vol2-modern-cpp17/index.md @@ -0,0 +1,30 @@ +--- +title: "现代模板技术(C++17)" +description: "C++17 现代模板技术:类型萃取、SFINAE、变参模板、折叠表达式等" +--- + +# 现代模板技术(C++17) + +> 状态:模板文章待编写,Ranges 文章已完成 + +本部分掌握现代 C++ 模板编程的核心工具,覆盖 type traits、SFINAE、if constexpr、变参模板、折叠表达式、完美转发、constexpr、CTAD。 + +## 模板文章(待编写) + +1. 类型萃取深度解析 +2. SFINAE 与 enable_if +3. if constexpr 编译期分支 +4. 可变参数模板 +5. 折叠表达式 +6. 完美转发 +7. constexpr 与编译期计算 +8. CTAD +9. 综合项目:类型安全的 any + +## 其他现代 C++ 特性 + + + 指定初始化器 + C++20 范围库基础与视图 + 管道操作与 Ranges 实战 + diff --git a/documents/vol4-advanced/vol3-metaprogramming-cpp20-23/.gitkeep b/documents/vol4-advanced/vol3-metaprogramming-cpp20-23/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/documents/vol4-advanced/vol3-metaprogramming-cpp20-23/index.md b/documents/vol4-advanced/vol3-metaprogramming-cpp20-23/index.md new file mode 100644 index 000000000..d77bc51f6 --- /dev/null +++ b/documents/vol4-advanced/vol3-metaprogramming-cpp20-23/index.md @@ -0,0 +1,22 @@ +--- +title: "元编程精要(C++20-23)" +description: "C++20/23 元编程:Concepts、requires 表达式、TMP 核心技巧、编译期字符串" +--- + +# 元编程精要(C++20-23) + +> 状态:待编写 + +本部分深入探讨现代 C++ 的约束机制和高级元编程技术,覆盖 Concepts、requires 表达式、TMP 核心技巧、编译期字符串、反射基础、实例化控制、异常安全。 + +## 本章内容(待编写) + +1. Concepts 详解 +2. 使用 Concepts 约束模板 +3. Requires 表达式深度解析 +4. TMP 核心技巧 +5. 编译期字符串处理 +6. 反射元编程基础 +7. 模板实例化控制 +8. 模板与异常安全 +9. 综合项目:mini-STL 算法库 diff --git a/documents/vol4-advanced/vol4-generics-patterns/.gitkeep b/documents/vol4-advanced/vol4-generics-patterns/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/documents/vol4-advanced/vol4-generics-patterns/index.md b/documents/vol4-advanced/vol4-generics-patterns/index.md new file mode 100644 index 000000000..dc1ae94b1 --- /dev/null +++ b/documents/vol4-advanced/vol4-generics-patterns/index.md @@ -0,0 +1,24 @@ +--- +title: "泛型设计模式实战" +description: "模板设计模式:Policy-Based Design、类型擦除、CRTP、DSL 等架构级应用" +--- + +# 泛型设计模式实战 + +> 状态:待编写 + +本部分将模板技术应用于实际架构设计,覆盖 Policy-Based Design、Type Erasure、NVI、工厂/访问者/单例/观察者模式的模板实现、Mixin 组合、Tag Dispatching、DSL 构建。 + +## 本章内容(待编写) + +1. Policy-Based Design +2. 类型擦除 +3. 模板方法模式与 NVI +4. 工厂模式模板实现 +5. 访问者模式模板实现 +6. 单例模式线程安全实现 +7. 观察者模式模板实现 +8. Mixin 与组合式设计 +9. Tag Dispatching 与类型分派 +10. 模板与 DSL +11. 综合项目:嵌入式事件系统 diff --git a/documents/vol8-domains/embedded/core-embedded-cpp-index.md b/documents/vol8-domains/embedded/core-embedded-cpp-index.md index 70b0a4993..02a7f87d0 100644 --- a/documents/vol8-domains/embedded/core-embedded-cpp-index.md +++ b/documents/vol8-domains/embedded/core-embedded-cpp-index.md @@ -49,7 +49,7 @@ order: 0 ## Chapter 4 - 编译期计算 -- [if_constexpr](../../vol4-advanced/04-if-constexpr.md) +- [if constexpr](../../vol4-advanced/vol3-metaprogramming-cpp20-23/index.md) ## Chapter 5 - 内存管理策略 @@ -88,12 +88,7 @@ order: 0 ## Chapter 12 - 模板基础 -- [模板入门概述](../../vol4-advanced/00-template-overview.md) -- [函数模板详解](../../vol4-advanced/01-function-templates.md) -- [类模板详解](../../vol4-advanced/02-class-templates.md) -- [模板特化与偏特化](../../vol4-advanced/03-template-specialization.md) -- [非类型模板参数](../../vol4-advanced/04-non-type-template-params.md) -- [模板参数依赖与名字查找](../../vol4-advanced/05-template-args-and-name-lookup.md) -- [模板友元与Barton-Nackman技巧](../../vol4-advanced/06-template-friends-and-barton-nackman.md) -- [模板别名与Using声明](../../vol4-advanced/07-template-aliases-and-using.md) -- [模板与继承CRTP](../../vol4-advanced/08-templates-and-inheritance-crtp.md) +- [模板基础(C++11-14)](../../vol4-advanced/vol1-basics-cpp11-14/index.md) +- [现代模板技术(C++17)](../../vol4-advanced/vol2-modern-cpp17/index.md) +- [元编程精要(C++20-23)](../../vol4-advanced/vol3-metaprogramming-cpp20-23/index.md) +- [泛型设计模式实战](../../vol4-advanced/vol4-generics-patterns/index.md) diff --git a/todo/038-template-vol1-basics.md b/todo/038-template-vol1-basics.md index 8e1df492f..68b4e652f 100644 --- a/todo/038-template-vol1-basics.md +++ b/todo/038-template-vol1-basics.md @@ -100,17 +100,17 @@ estimated_effort: epic - [ ] 综合运用:类模板、非类型参数、特化、CRTP、友元 ## 涉及文件 -- documents/vol4-advanced/ch05-template-metaprogramming/vol1-basics/index.md -- documents/vol4-advanced/ch05-template-metaprogramming/vol1-basics/01-introduction.md -- documents/vol4-advanced/ch05-template-metaprogramming/vol1-basics/02-function-templates.md -- documents/vol4-advanced/ch05-template-metaprogramming/vol1-basics/03-class-templates.md -- documents/vol4-advanced/ch05-template-metaprogramming/vol1-basics/04-specialization.md -- documents/vol4-advanced/ch05-template-metaprogramming/vol1-basics/05-nttp.md -- documents/vol4-advanced/ch05-template-metaprogramming/vol1-basics/06-name-lookup.md -- documents/vol4-advanced/ch05-template-metaprogramming/vol1-basics/07-friends-barton-nackman.md -- documents/vol4-advanced/ch05-template-metaprogramming/vol1-basics/08-aliases.md -- documents/vol4-advanced/ch05-template-metaprogramming/vol1-basics/09-inheritance-crtp.md -- documents/vol4-advanced/ch05-template-metaprogramming/vol1-basics/10-project-fixed-vector.md +- documents/vol4-advanced/vol1-basics-cpp11-14/index.md +- documents/vol4-advanced/vol1-basics-cpp11-14/01-template-introduction.md +- documents/vol4-advanced/vol1-basics-cpp11-14/02-function-templates.md +- documents/vol4-advanced/vol1-basics-cpp11-14/03-class-templates.md +- documents/vol4-advanced/vol1-basics-cpp11-14/04-specialization.md +- documents/vol4-advanced/vol1-basics-cpp11-14/05-nttp.md +- documents/vol4-advanced/vol1-basics-cpp11-14/06-name-lookup.md +- documents/vol4-advanced/vol1-basics-cpp11-14/07-friends-barton-nackman.md +- documents/vol4-advanced/vol1-basics-cpp11-14/08-aliases.md +- documents/vol4-advanced/vol1-basics-cpp11-14/09-inheritance-crtp.md +- documents/vol4-advanced/vol1-basics-cpp11-14/10-project-fixed-vector.md ## 参考资料 - 《C++ Templates: The Complete Guide》(2nd Edition) Part I & II diff --git a/todo/039-template-vol2-modern.md b/todo/039-template-vol2-modern.md index 8f398a9c0..5dbf83389 100644 --- a/todo/039-template-vol2-modern.md +++ b/todo/039-template-vol2-modern.md @@ -104,16 +104,16 @@ estimated_effort: epic - [ ] 与 `std::any` 的接口兼容与性能对比 ## 涉及文件 -- documents/vol4-advanced/ch05-template-metaprogramming/vol2-modern/index.md -- documents/vol4-advanced/ch05-template-metaprogramming/vol2-modern/01-type-traits.md -- documents/vol4-advanced/ch05-template-metaprogramming/vol2-modern/02-sfinae.md -- documents/vol4-advanced/ch05-template-metaprogramming/vol2-modern/03-if-constexpr.md -- documents/vol4-advanced/ch05-template-metaprogramming/vol2-modern/04-variadic-templates.md -- documents/vol4-advanced/ch05-template-metaprogramming/vol2-modern/05-fold-expressions.md -- documents/vol4-advanced/ch05-template-metaprogramming/vol2-modern/06-perfect-forwarding.md -- documents/vol4-advanced/ch05-template-metaprogramming/vol2-modern/07-constexpr.md -- documents/vol4-advanced/ch05-template-metaprogramming/vol2-modern/08-ctad.md -- documents/vol4-advanced/ch05-template-metaprogramming/vol2-modern/09-project-any.md +- documents/vol4-advanced/vol2-modern-cpp17/index.md +- documents/vol4-advanced/vol2-modern-cpp17/01-type-traits.md +- documents/vol4-advanced/vol2-modern-cpp17/02-sfinae.md +- documents/vol4-advanced/vol2-modern-cpp17/03-if-constexpr.md +- documents/vol4-advanced/vol2-modern-cpp17/04-variadic-templates.md +- documents/vol4-advanced/vol2-modern-cpp17/05-fold-expressions.md +- documents/vol4-advanced/vol2-modern-cpp17/06-perfect-forwarding.md +- documents/vol4-advanced/vol2-modern-cpp17/07-constexpr.md +- documents/vol4-advanced/vol2-modern-cpp17/08-ctad.md +- documents/vol4-advanced/vol2-modern-cpp17/09-project-any.md ## 参考资料 - 《C++ Templates: The Complete Guide》(2nd Edition) Part III & IV diff --git a/todo/040-template-vol3-metaprogramming.md b/todo/040-template-vol3-metaprogramming.md index c472c297f..57c6ca800 100644 --- a/todo/040-template-vol3-metaprogramming.md +++ b/todo/040-template-vol3-metaprogramming.md @@ -96,16 +96,16 @@ estimated_effort: epic - [ ] 嵌入式裁剪版本:无异常、无 RTTI、固定大小容器优先 ## 涉及文件 -- documents/vol4-advanced/ch05-template-metaprogramming/vol3-metaprogramming/index.md -- documents/vol4-advanced/ch05-template-metaprogramming/vol3-metaprogramming/01-concepts.md -- documents/vol4-advanced/ch05-template-metaprogramming/vol3-metaprogramming/02-constraining-templates.md -- documents/vol4-advanced/ch05-template-metaprogramming/vol3-metaprogramming/03-requires-expressions.md -- documents/vol4-advanced/ch05-template-metaprogramming/vol3-metaprogramming/04-tmp-techniques.md -- documents/vol4-advanced/ch05-template-metaprogramming/vol3-metaprogramming/05-compile-time-strings.md -- documents/vol4-advanced/ch05-template-metaprogramming/vol3-metaprogramming/06-reflection-basics.md -- documents/vol4-advanced/ch05-template-metaprogramming/vol3-metaprogramming/07-instantiation-control.md -- documents/vol4-advanced/ch05-template-metaprogramming/vol3-metaprogramming/08-exception-safety.md -- documents/vol4-advanced/ch05-template-metaprogramming/vol3-metaprogramming/09-project-mini-stl.md +- documents/vol4-advanced/vol3-metaprogramming-cpp20-23/index.md +- documents/vol4-advanced/vol3-metaprogramming-cpp20-23/01-concepts.md +- documents/vol4-advanced/vol3-metaprogramming-cpp20-23/02-constraining-templates.md +- documents/vol4-advanced/vol3-metaprogramming-cpp20-23/03-requires-expressions.md +- documents/vol4-advanced/vol3-metaprogramming-cpp20-23/04-tmp-techniques.md +- documents/vol4-advanced/vol3-metaprogramming-cpp20-23/05-compile-time-strings.md +- documents/vol4-advanced/vol3-metaprogramming-cpp20-23/06-reflection-basics.md +- documents/vol4-advanced/vol3-metaprogramming-cpp20-23/07-instantiation-control.md +- documents/vol4-advanced/vol3-metaprogramming-cpp20-23/08-exception-safety.md +- documents/vol4-advanced/vol3-metaprogramming-cpp20-23/09-project-mini-stl.md ## 参考资料 - 《C++ Templates: The Complete Guide》(2nd Edition) Part V diff --git a/todo/041-template-vol4-design-patterns.md b/todo/041-template-vol4-design-patterns.md index b7d46d1c8..5b17bc8b6 100644 --- a/todo/041-template-vol4-design-patterns.md +++ b/todo/041-template-vol4-design-patterns.md @@ -108,18 +108,18 @@ estimated_effort: epic - [ ] 性能验证:中断延迟测量 + 内存占用分析 ## 涉及文件 -- documents/vol4-advanced/ch05-template-metaprogramming/vol4-design-patterns/index.md -- documents/vol4-advanced/ch05-template-metaprogramming/vol4-design-patterns/01-policy-based-design.md -- documents/vol4-advanced/ch05-template-metaprogramming/vol4-design-patterns/02-type-erasure.md -- documents/vol4-advanced/ch05-template-metaprogramming/vol4-design-patterns/03-nvi-template-method.md -- documents/vol4-advanced/ch05-template-metaprogramming/vol4-design-patterns/04-factory-pattern.md -- documents/vol4-advanced/ch05-template-metaprogramming/vol4-design-patterns/05-visitor-pattern.md -- documents/vol4-advanced/ch05-template-metaprogramming/vol4-design-patterns/06-singleton-pattern.md -- documents/vol4-advanced/ch05-template-metaprogramming/vol4-design-patterns/07-observer-pattern.md -- documents/vol4-advanced/ch05-template-metaprogramming/vol4-design-patterns/08-mixin-composition.md -- documents/vol4-advanced/ch05-template-metaprogramming/vol4-design-patterns/09-tag-dispatching.md -- documents/vol4-advanced/ch05-template-metaprogramming/vol4-design-patterns/10-dsl.md -- documents/vol4-advanced/ch05-template-metaprogramming/vol4-design-patterns/11-project-event-system.md +- documents/vol4-advanced/vol4-generics-patterns/index.md +- documents/vol4-advanced/vol4-generics-patterns/01-policy-based-design.md +- documents/vol4-advanced/vol4-generics-patterns/02-type-erasure.md +- documents/vol4-advanced/vol4-generics-patterns/03-nvi-template-method.md +- documents/vol4-advanced/vol4-generics-patterns/04-factory-pattern.md +- documents/vol4-advanced/vol4-generics-patterns/05-visitor-pattern.md +- documents/vol4-advanced/vol4-generics-patterns/06-singleton-pattern.md +- documents/vol4-advanced/vol4-generics-patterns/07-observer-pattern.md +- documents/vol4-advanced/vol4-generics-patterns/08-mixin-composition.md +- documents/vol4-advanced/vol4-generics-patterns/09-tag-dispatching.md +- documents/vol4-advanced/vol4-generics-patterns/10-dsl.md +- documents/vol4-advanced/vol4-generics-patterns/11-project-event-system.md ## 参考资料 - 《Modern C++ Design》— Andrei Alexandrescu(Policy-Based Design 开山之作)