目录
1. 基础语法与关键字
C++98/03 const/volatile作用 · static作用域 · 指针与引用区别 · 类型转换(4种cast) · 宏与预处理器
C++11 auto类型推导 · nullptr · decltype · 范围for循环 · 原始字符串字面量 · alignof/alignas · using别名
C++14 二进制字面量 · 数字分隔符 · 泛型lambda · 函数返回类型推导 · [[deprecated]]属性
C++17 结构化绑定 · if/switch初始化语句 · 内联变量 · 折叠表达式 · std::byte
C++20 指定初始化 · 协程关键字(co_await等) · 模块(import) · 属性增强([[no_unique_address]])
2. 面向对象特性
C++98/03 类访问控制 · 构造/析构函数 · 继承与多态 · 虚函数表(vptr) · 运算符重载 · RTTI机制
C++11 委托构造 · 继承构造 · final/override · 强类型枚举 · 移动语义 · 显式缺省/删除函数(=default/=delete)
C++14 成员函数返回类型推导 · 放宽const成员函数约束
C++17 聚合体扩展初始化 · 内联静态成员 · 嵌套命名空间简化(namespace A::B)
C++20 概念约束类模板 · 协程成员函数 · 三路比较运算符(<=>) · 基于范围的operator[]
类与对象 继承与多态 构造函数与析构函数 虚函数与纯虚函数 拷贝构造函数与赋值运算符 友元函数与类 面向对象设计原则(SOLID)
3. 模板与泛型编程
C++98/03 函数/类模板基础 · 模板特化/偏特化 · 依赖名称解析(typename双义性)
C++11 可变参数模板 · 模板别名 · 外部模板 · 类型萃取(type_traits) · SFINAE与enable_if
C++14 变量模板 · 泛型lambda捕获 · 函数模板默认参数
C++17 类模板参数推导(CTAD) · 折叠表达式 · if constexpr · 结构化绑定模板
C++20 概念(concepts) · 约束模板 · 模板lambda · 简写函数模板 · 模板参数列表简化
函数模板与类模板 模板特化与偏特化 模板元编程(模板递归、SFINAE) 类型萃取(Type Traits) 泛型编程的应用(STL 容器等)
4. 内存管理
C++98/03 new/delete机制 · 内存分区(栈/堆等) · 深浅拷贝 · 定位new表达式
C++11 移动语义 · 右值引用 · 智能指针(unique_ptr/shared_ptr/weak_ptr) · 内存模型 · allocator_traits
C++14 make_unique · 共享指针数组支持 · 大小明确的delete
C++17 内存对齐控制 · 多态分配器(std::pmr) · 未初始化内存工具
C++20 原子智能指针 · 销毁操作概念化 · 协程内存优化
内存分配与释放(new/delete,malloc/free) 智能指针(std::unique_ptr, std::shared_ptr, std::weak_ptr) 内存泄漏与野指针 自定义内存管理
5. 异常处理
C++98/03 基本try-catch机制 · 异常规范(已弃用) · RAII模式 · 异常安全保证
C++11 noexcept关键字 · 异常传播改进 · exception_ptr(跨线程异常)
C++17 uncaught_exceptions()(嵌套异常) · 异常类型系统增强
C++20 协程异常处理 · 契约编程(未正式采纳) · 异常概念化
6. 并发与多线程
C++11 std::thread · 互斥量(mutex) · 条件变量 · future/promise · 原子操作库
C++14 共享锁超时 · 泛型原子操作 · 读写锁(shared_timed_mutex)
C++17 并行算法(std::execution) · scoped_lock · 共享指针原子操作 · 文件系统库
C++20 协程支持 · 信号量(counting_semaphore) · 屏障(barrier) · jthread(自动join) · 原子等待
7. 标准库关键组件
C++11 智能指针 · std::function/std::bind · 正则表达式 · 哈希容器 · 随机数库
C++14 自定义字面量 · 编译时整数序列
C++17 std::optional · std::variant · std::any · std::string_view · 并行算法
C++20 std::span(无所有权视图) · std::format(格式化) · 范围库(Ranges) · 日历与时区
8. 编译时计算
C++11 constexpr函数 · 常量表达式基础
C++14 constexpr扩展(分支/循环) · 变量模板的constexpr
C++17 if constexpr · constexpr lambda · 内联变量编译时初始化
C++20 consteval(立即函数) · constinit · 编译时虚函数 · 编译时容器(std::vector)
9. 初始化演进
C++98/03 构造函数初始化列表 · 值初始化
C++11 统一初始化({}语法) · std::initializer_list · 列表初始化优先级
C++20 指定初始化(Designated Initializers) · 聚合初始化扩展
virtual
1class Shape {
2public:
3 virtual void draw() = 0; // 纯虚函数 抽象接口 派生类必须实现
4 virtual ~Shape() = default; // 基类的析构函数应为虚函数 使用默认生成的函数实现
5};
6
7// = 0 表示纯虚函数
8// = default 表示使用编译器生成的默认实现
9
10#include <iostream>
11#include <vector>
12
13struct Base {
14 std::vector<int> data{1, 2, 3};
15 ~Base() = default; // 默认析构
16};
17
18int main() {
19 Base b;
20}
21
22// 编译器会把 ~Base() = default; 变成类似:
23
24~Base() {
25 data.~vector(); // 自动调用 vector<int> 的析构函数
26}
析构函数应为虚函数,防止通过基类指针删除派生类对象时,派生类的析构函数不被调用,导致资源泄漏。
1class Base {
2public:
3 ~Base() { std::cout << "Base dtor\n"; }
4};
5
6class Derived : public Base {
7public:
8 ~Derived() { std::cout << "Derived dtor\n"; }
9};
10
11Base* p = new Derived();
12delete p; // 只会调用 Base::~Base(),不会调用 Derived::~Derived()
智能指针
std::unique_ptr
std::unique_ptr
- 独占所有权:同一时间只能有一个 unique_ptr 指向同一对象
- 不可拷贝(拷贝构造/赋值被禁用),但可移动
- 生命周期结束时会自动调用 delete 释放资源
1#include <iostream>
2#include <memory>
3
4int main() {
5 std::unique_ptr<int> p1(new int(42)); // 管理一个int
6 std::cout << *p1 << "\n";
7
8 // std::unique_ptr<int> p2 = p1; // ❌ 不允许拷贝
9 std::unique_ptr<int> p2 = std::move(p1); // ✅ 允许移动
10 if (!p1) std::cout << "p1 is empty\n";
11}
什么推荐 make_unique 而不是 new?
-
异常安全:
c++ 17前,求值顺序未定义。先执行 new T,然后可能在传参时 g() 抛异常,结果 new 的结果还没交给unique_ptr,内存泄漏。
1f(std::unique_ptr<T>(new T), g());
make_unique 内部是一条语句,new 出来的对象会立刻交给 unique_ptr,不会存在裸指针暂存的情况,资源会在异常传播时析构。
1f(std::make_unique<T>(), g());
-
更简洁:不需要显式写 new。
-
避免重复类型:
1std::unique_ptr<MyClass> p1(new MyClass(1,2,3)); // 类型写了两遍 2auto p2 = std::make_unique<MyClass>(1,2,3); // 类型只写一次
局部静态变量
静态初始化顺序问题
静态初始化顺序问题(Static Initialization Order Fiasco)。C++ 中全局/静态变量的初始化顺序:
- 同一个翻译单元(.cpp 文件),按照出现顺序初始化。
- 不同翻译单元,初始化顺序是未定义的。
1// ShapeFactory.h
2class ShapeFactory {
3private:
4 static std::unordered_map<std::string, Creator> registry;
5public:
6 static void registerShape(const std::string& name, Creator c) {
7 registry[name] = c;
8 }
9};
10
11// ShapeFactory.cpp
12std::unordered_map<std::string, ShapeFactory::Creator> ShapeFactory::registry;
在 另一个翻译单元 有静态对象注册:
1// Circle.cpp
2struct CircleRegister {
3 CircleRegister() {
4 ShapeFactory::registerShape("circle", [](){ return std::make_unique<Circle>(); });
5 }
6};
7static CircleRegister cr;
如果 ShapeFactory::registry 还没初始化,registerShape 就会访问未定义内存,程序可能崩溃或行为异常。这个问题叫 Static Initialization Order Fiasco(静态初始化顺序地狱)。
使用 getRegistry() 的好处
1static std::unordered_map<std::string, Creator>& getRegistry() {
2 static std::unordered_map<std::string, Creator> registry;
3 return registry;
4}
局部静态变量 在第一次调用时初始化,而不是编译时。这样无论 registerShape() 被哪个翻译单元的静态对象调用,registry 一定已经初始化。完全避免初始化顺序问题。
全局可见性问题(多个翻译单元)
也是初始化顺序问题的一种,在多个翻译单元中出现。假设你有三个 .cpp 文件:
1// Circle.cpp
2static struct { CircleRegister() { ShapeFactory::registerShape("circle", ...); } } cr;
3
4// Rectangle.cpp
5static struct { RectangleRegister() { ShapeFactory::registerShape("rectangle", ...); } } rr;
6
7// main.cpp
8int main() { ... }
静态成员变量 registry 是全局的,但初始化顺序不确定。如果 Circle.cpp 中的静态对象先调用 registerShape(),而 registry 还没初始化,就会访问未定义内存。局部静态函数延迟初始化,保证第一次使用时再创建 registry,完全解决跨翻译单元依赖问题。
线程安全问题
- C++11前:静态成员变量初始化 不是线程安全。多个线程同时访问静态成员变量的初始化,可能会导致竞态条件。
- C++11后:函数内部静态变量初始化是 线程安全的。
1void registerShapes() {
2 std::thread t1([](){ ShapeFactory::registerShape("circle", ...); });
3 std::thread t2([](){ ShapeFactory::registerShape("rectangle", ...); });
4 t1.join();
5 t2.join();
6}
如果 registry 是静态成员变量,C++11 之前可能出现访问冲突。使用局部静态变量,C++11+ 编译器保证初始化只执行一次,安全。
不同类型资源的风险等级
资源类型 | OS清理 | 风险等级 | 主要问题 |
---|---|---|---|
内存 | ✅ 完全清理 | 🟢 低 | 主要是逻辑问题 |
文件描述符 | ✅ 强制关闭 | 🟡 中 | 数据可能丢失 |
网络连接 | ✅ 强制断开 | 🟠 中高 | 服务端资源泄漏 |
文件锁 | ✅ 自动释放 | 🟡 中 | 锁文件可能残留 |
临时文件 | ❌ 不会删除 | 🔴 高 | 磁盘空间浪费 |
数据库连接 | ❌ 不会清理 | 🔴 高 | 连接池耗尽 |
共享内存 | ⚠️ 部分清理 | 🟠 中高 | 可能需要手动清理 |
explicit
explicit 关键字用于修饰构造函数,防止隐式类型转换。
1// 没有explicit的类
2class Observer {
3public:
4 Observer(const std::string& name) {
5 std::cout << "Observer: " << name << std::endl;
6 }
7};
8
9// 有explicit的类
10class File {
11public:
12 explicit File(const std::string& filename) {
13 std::cout << "File: " << filename << std::endl;
14 }
15};
16
17void process(Observer obs) {
18 std::cout << "Processing observer\n";
19}
20
21int main() {
22 // 1. 赋值 - 没有explicit时可以这样写
23 Observer obs = "Alice"; // ❌ 意外:看起来在赋值字符串
24
25 // 2. 函数传参 - 没有explicit时可以这样写
26 process("Bob"); // ❌ 意外:创建了临时Observer对象
27
28 // 3. 文件例子 - 有explicit时必须明确
29 // File f = "test.txt"; // ❌ 编译错误!
30 File f("test.txt"); // ✅ 必须明确创建
31
32 return 0;
33}