Skip to content

C++ 学习笔记 ​

#语言 · #C++ · #CPP-Primer · #Effective-CPP · #Modern-CPP

C++ 不是"带类的 C"——它是支持过程式、面向对象、泛型、函数式四种范式的多范式系统编程语言。 本文融合 C++ Primer (5th) 的基础体系、Effective C++ 的 55 条军规、以及 C++11/14/17/20/23 的最新发展,结合 GCC/Clang 编译器技术,构建可运行的完整知识体系。


一、C++ 全景:从 C++98 到 C++26 ​

1.1 语言演进 ​

mermaid
timeline
    title C++ 标准演进
    1998 : C++98 : 第一个标准,STL、模板、异常
    2003 : C++03 : 缺陷修复
    2011 : C++11 : 革命:auto/lambda/智能指针/移动语义/并发
    2014 : C++14 : 泛型 lambda、make_unique
    2017 : C++17 : 结构化绑定、optional/variant、string_view、if constexpr
    2020 : C++20 : 概念(concepts)、范围(ranges)、协程、模块、三路比较 <=>
    2023 : C++23 : std::expected、ranges 完善、deducing this、flat_map/set
    2026 : C++26 : 反射、contracts、std::execution(进行中)

关键认知:从 C++11 开始,C++ 进入"现代 C++"时代。核心哲学从"相信程序员"转向"让编译器帮你检查"。如果你看到 new/delete 裸指针、NULL 宏、手写 for 循环——那多半是过时的写法。

1.2 三大编译器对比 ​

特性GCCClang/LLVMMSVC
前端cc1plus(自研)Clang(模块化设计)C1XX
后端GCC 后端(RTL)LLVM IR → 多目标自研
优化激进/有时过于激进保守/更安全中等
诊断可读性一般最强(彩色箭头指向错误位置)中等
C++23 支持✅ 大部分✅ 大部分⚠️ 部分
modules 支持实验性(14+)✅ (17+)✅ (VS2022 17.5+)
sanitizersASan/TSan/UBSanASan/TSan/UBSan/MSanASan
工具链gdblldb + lldWinDbg
编译命令g++ -std=c++23 -O2clang++ -std=c++23 -O2cl /std:c++latest /O2

推荐开发环境:Linux/macOS 上用 Clang(诊断信息最好);Windows 上用 MSVC(原生支持最好)。日常开发同时用 GCC 和 Clang 编译验证可移植性。

1.3 编译与链接全流程 ​

mermaid
flowchart LR
    subgraph Preprocess["预处理"]
        S1["main.cpp"] --> PP["g++ -E<br/>头文件展开<br/>宏替换<br/>条件编译"]
    end
    subgraph Compile["编译"]
        PP --> CC["g++ -S<br/>C++ → 汇编<br/>(语法分析→AST→优化→代码生成)"]
    end
    subgraph Assemble["汇编"]
        CC --> AS["g++ -c<br/>汇编 → .o"]
    end
    subgraph Link["链接"]
        AS --> LD["ld<br/>符号解析 + 重定位<br/>合并 .o → 可执行文件"]
        LIB["libstdc++.a<br/>标准库"] --> LD
    end
    LD --> EXE["a.out / .exe"]
bash
# 一步编译
g++ -std=c++23 -O2 -Wall -Wextra -o program main.cpp

# 分步查看中间产物
g++ -E main.cpp -o main.ii          # 预处理结果
g++ -S main.cpp -o main.s            # 汇编代码
g++ -c main.cpp -o main.o            # 目标文件
objdump -d main.o | head -30         # 反汇编查看实际指令

1.4 C++ 对象模型速览:空类多大? ​

cpp
class Empty {};                     // sizeof = 1  (C++ 保证每个对象有唯一地址)
class HasInt { int x; };            // sizeof = 4  (仅成员)
class HasVirtual { virtual void f(); }; // sizeof = 8  (vptr,64位系统)
class HasVAndInt : public HasVirtual { int y; }; // sizeof = 16 (vptr 8B + int 4B + padding 4B)

struct Layout {
    char c;     // 1B, offset 0
    int i;      // 4B, offset 4  (对齐到 4)
    short s;    // 2B, offset 8
    char c2;    // 1B, offset 10
    // padding 1B → sizeof = 12 (对齐到最大成员 4 的倍数)
};
bash
# 查看实际内存布局
clang++ -Xclang -fdump-record-layouts main.cpp
# 输出:
# *** Dumping AST Record Layout
#    0 | struct Layout
#    0 |   char c
#    4 |   int i
#    8 |   short s
#   10 |   char c2
#      | [sizeof=12, align=4]

二、变量和基本类型(C++ Primer Ch2) ​

2.1 基本类型的内存模型 ​

类型大小(64位)范围字面量后缀
bool1true/false—
char1-128~127 或 0~255'a'
wchar_t4实现定义L'a'
char8_t(C++20)1UTF-8u8'a'
char16_t(C++11)2UTF-16u'a'
char32_t(C++11)4UTF-32U'a'
short2-32768~32767—
int4±2.1×10⁹42
long8±9.2×10¹⁸42L
long long(C++11)8±9.2×10¹⁸42LL
float4±3.4×10³⁸, ~7位3.14f
double8±1.7×10³⁰⁸, ~15位3.14
long double16扩展精度3.14L
cpp
// 类型体系验证
static_assert(sizeof(bool) == 1);
static_assert(sizeof(int) >= 2);   // C++ 只保证 int ≥ short ≥ 16bit
static_assert(sizeof(void*) == 8); // 64位系统

// 数值极限(不要记常数,查 traits)
#include <limits>
constexpr int INT_MIN = std::numeric_limits<int>::min();  // -2147483648
constexpr float EPS   = std::numeric_limits<float>::epsilon(); // ~1.19e-07

2.2 初始化:C++ 的"千层套路" ​

cpp
// === 六种初始化方式 ===
int a = 0;          // 拷贝初始化
int b(0);           // 直接初始化
int c{0};           // 列表初始化 (C++11, 推荐!)
int d = {0};        // 拷贝列表初始化
auto e = 0;         // auto 类型推导
int f;              // 默认初始化(局部变量:未定义!全局/静态:0)

// 列表初始化的核心优势:阻止窄化转换
int x1 = 3.14;      // ✅ 编译通过,x1 = 3(丢失精度)
// int x2{3.14};    // ❌ 编译错误:narrowing conversion
int x3 = {3.14};    // ❌ 同样编译错误

// 最令人烦恼的解析 (Most Vexing Parse)
struct Timer {};
Timer t1;           // ✅ 默认构造
// Timer t2();      // ❌ 函数声明!声明了返回 Timer 的函数 t2
Timer t3{};         // ✅ 列表初始化避免歧义

2.3 auto 与 decltype(C++11) ​

cpp
// auto:让编译器推导类型(但去除引用和顶层 const)
const int& ri = 42;
auto x = ri;        // x 是 int(去掉了 & 和 const)
auto& y = ri;       // y 是 const int&

// decltype:获取表达式的精确类型(不计算表达式)
decltype(ri) z = x; // z 是 const int&(保留全部修饰符)

// decltype(auto) (C++14):保留引用性
int&& rr = 42;
decltype(auto) dd = std::move(rr); // dd 是 int&&

// auto 实战:告别冗长的迭代器
std::map<std::string, std::vector<int>> m;
// std::map<std::string, std::vector<int>>::iterator it = m.begin();  // C++98
auto it = m.begin(); // C++11
for (const auto& [key, val] : m) {} // C++17 结构化绑定

2.4 指针、引用与 nullptr(C++11) ​

cpp
// 指针 vs 引用
int val = 42;
int* p = &val;      // 指针:可为空,可重新指向
int& r = val;       // 引用:必须初始化,不可改为绑定其他对象
r = 10;             // 等价于 val = 10,不是改变绑结

// nullptr (C++11) 替代 NULL/0
void f(int);
void f(void*);
f(0);               // 调用 f(int),是不是很意外?
f(nullptr);         // 调用 f(void*),明确无误

三、字符串、向量和数组(C++ Primer Ch3) ​

3.1 std::string 深剖 ​

cpp
#include <string>
#include <string_view>  // C++17

std::string s1 = "hello";           // C 字符串转换
std::string s2(10, 'a');           // "aaaaaaaaaa"
std::string s3 = s1 + " world";    // 连接

// C++17 string_view:零拷贝只读视图
std::string_view sv = "hello world";
auto sub = sv.substr(0, 5);          // O(1),不复制!
// sv[0] = 'H';                      // ❌ 只读

// 常用操作(注意复杂度)
s1.size();       // O(1), C++11 起保证
s1.empty();      // O(1)
s1.find("ll");   // O(n·m), 朴素匹配(不如 KMP 但常数小)
s1.starts_with("he");  // C++20
s1.ends_with("lo");    // C++20

3.2 std::vector:你的默认容器 ​

cpp
#include <vector>

std::vector<int> v1;                // 空
std::vector<int> v2(10);            // 10 个 0
std::vector<int> v3(10, 42);        // 10 个 42
std::vector<int> v4 = {1, 2, 3};    // 列表初始化
std::vector<int> v5(v4.begin(), v4.begin() + 2); // [1, 2]

// 内存模型:三指针实现
// ┌──────┬──────┬──────┐
// │ begin│ end  │ cap  │ → 连续内存,O(1) 随机访问
// └──┬───┴──┬───┴──┬───┘
//    │      │      │
//    ▼      ▼      ▼
// ┌──┬──┬──┬──┬──┐
// │ 1│ 2│ 3│  │  │  capacity=5, size=3
// └──┴──┴──┴──┴──┘

v4.push_back(4);    // 尾部追加,均摊 O(1)
v4.emplace_back(5); // C++11: 原地构造,避免拷贝
v4.pop_back();      // O(1)
v4.at(0) = 10;      // 带边界检查
v4[0] = 10;         // 不带检查

3.3 数组与 STL 算法 ​

cpp
// C 风格数组 vs std::array
int c_arr[5] = {1, 2, 3, 4, 5};
std::array<int, 5> cpp_arr = {1, 2, 3, 4, 5};  // C++11
// cpp_arr 不会退化为指针,知道自己的 size!

// == 迭代器 ==
// 迭代器层次:Input → Forward → Bidirectional → RandomAccess
//                  ↕         ↕             ↕             ↕
//               istream   forward_list    list         vector/deque/array

// STL 算法(不要手写循环!)
#include <algorithm>
#include <numeric>
std::vector<int> nums = {3, 1, 4, 1, 5, 9, 2, 6};

// 查找
auto it = std::find(nums.begin(), nums.end(), 5);
bool all_pos = std::all_of(nums.begin(), nums.end(), [](int x){ return x > 0; }); // C++11

// 排序与二分
std::sort(nums.begin(), nums.end());                    // O(n log n),快排/内省排序
bool found = std::binary_search(nums.begin(), nums.end(), 4); // O(log n)
auto [lb, ub] = std::equal_range(nums.begin(), nums.end(), 4); // C++17: pair → 结构化绑定

// 变换
std::vector<int> doubled(nums.size());
std::transform(nums.begin(), nums.end(), doubled.begin(),
               [](int x) { return x * 2; });

// 删除(erase-remove 惯用法)
nums.erase(std::remove(nums.begin(), nums.end(), 1), nums.end());
// 原来:{3,1,4,1,5,9,2,6} → {3,4,5,9,2,6}

// 求和
int sum = std::accumulate(nums.begin(), nums.end(), 0);     // 55
int prod = std::reduce(nums.begin(), nums.end(), 1,
                        std::multiplies<>{});               // C++17 可并行

// C++20 ranges:告别 begin/end 对
#include <ranges>
namespace rv = std::ranges::views;
auto result = nums
    | rv::filter([](int x) { return x % 2 == 0; })  // 仅偶数
    | rv::transform([](int x) { return x * x; })     // 平方
    | rv::take(3);                                    // 前三个
// result: {16, 16, 36}   (惰性求值!)

3.4 多维数组与 std::mdspan(C++23) ​

cpp
// 传统多维数组
int matrix[3][4] = {
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9,10,11,12}
};
int (*p)[4] = matrix;  // 指向 int[4] 的指针

// C++23 mdspan:多维视图
#include <mdspan>
std::vector<int> flat = {1,2,3,4,5,6,7,8,9,10,11,12};
auto ms = std::mdspan(flat.data(), 3, 4);
// ms[1, 2] == 7    (行列索引)

四、表达式和语句(C++ Primer Ch4-5) ​

4.1 类型转换:显式优于隐式 ​

cpp
// 命名的强制转换(C++ 风格,推荐)
// static_cast:相关类型间的编译期转换
double d = 3.14;
int i = static_cast<int>(d);     // 截断
void* vp = &i;
int* ip = static_cast<int*>(vp); // void* → T*

// const_cast:去掉/添加 const(仅此一个用途)
const char* str = "hello";
char* mutable_str = const_cast<char*>(str); // 危险!仅当原对象本身非 const 时安全

// reinterpret_cast:位级别的重新解释(最危险)
long addr = reinterpret_cast<long>(ip);     // 指针 → 整数

// dynamic_cast:运行时多态转换(见后续 OOP 章节)
struct Base { virtual ~Base() = default; };
struct Derived : Base {};
Derived d2;
Base* bp = &d2;
// dynamic_cast 失败返回 nullptr(对指针)或抛出 std::bad_cast(对引用)
auto* dp = dynamic_cast<Derived*>(bp); // ✅

// C 风格转换:不推荐(编译器不区分意图)
int x = (int)3.14;  // 到底是 static_cast 还是 reinterpret_cast?

4.2 控制流:现代 C++ 的改进 ​

cpp
// if 语句中的初始化器 (C++17)
std::map<int, std::string> m;
if (auto [it, inserted] = m.insert({1, "one"}); inserted) {
    std::cout << "Inserted: " << it->second << "\n";
}
// it, inserted 只在此 if 作用域内可见

// if constexpr (C++17):编译期分支
template<typename T>
auto get_value(T t) {
    if constexpr (std::is_pointer_v<T>) {
        return *t;      // 仅当 T 是指针时编译此分支
    } else {
        return t;       // 否则编译此分支
    }
}

// 范围 for + 结构化绑定 (C++17)
std::map<std::string, int> scores = {{"Alice", 95}, {"Bob", 87}};
for (const auto& [name, score] : scores) {
    std::cout << name << ": " << score << "\n";
}

4.3 异常处理 ​

cpp
// 异常安全等级
// 1. 基本保证:不泄露资源,对象可析构
// 2. 强保证:操作要么成功,要么回滚到操作前状态
// 3. 不抛异常保证:操作绝不会抛出异常 noexcept

// noexcept 规范 (C++11)
void may_throw();                    // 可能抛异常
void never_throw() noexcept;         // 承诺不抛异常
static_assert(noexcept(never_throw()));

// RAII + 异常:自动清理
void process_file(const char* name) {
    std::ifstream f(name);  // 构造时打开文件
    // ... 文件操作(可能抛异常)
}   // 析构时自动关闭文件,无需手动 f.close()
// 核心:让析构函数处理清理,而不是 try-catch 每个操作

// C++17 std::optional:替代异常表示"可能无值"
std::optional<int> safe_div(int a, int b) {
    if (b == 0) return std::nullopt;
    return a / b;
}
if (auto result = safe_div(10, 0)) {
    std::cout << *result;
} else {
    std::cout << "division by zero";
}

五、函数(C++ Primer Ch6) ​

5.1 参数传递:值 vs 引用 vs 指针 ​

cpp
// 规则:"传引用"的代价约等于传指针 ≈ 8 字节
// 对基本类型(≤ 16 字节):传值可能更高效(寄存器传参)
// 对类类型:传 const 引用

void by_value(std::string s);          // 拷贝,s 是局部量
void by_const_ref(const std::string& s); // 不拷贝,只读
void by_ref(std::string& s);           // 可以修改实参

// 现代 C++ 习惯:对"只读大对象"传 const T& 或 T&&(万能引用)
// 对"可能需要拷贝的参数"传值再 move
void sink(std::string s) {
    data_ = std::move(s);  // s 是局部量,可以安全移动
}
mermaid
flowchart LR
    subgraph PassBy["参数传递决策树"]
        Q1{"参数 ≤ 16B<br/>且平凡可拷贝?"}
        Q2{"需要修改实参?"}
        Q3{"只需要读?"}
        Q4{"需要持有副本?"}
    end
    Q1 -->|是| BYVAL["传值 (T)"]
    Q1 -->|否| Q2
    Q2 -->|是| BYREF["传引用 (T&)"]
    Q2 -->|否| Q3
    Q3 -->|是| BCREF["传 const 引用 (const T&)"]
    Q3 -->|否| Q4
    Q4 -->|是| SINK["传值 + move (T)"]

5.2 函数重载与默认实参 ​

cpp
// 重载决议优先级(匹配精确度递减):
// 1. 精确匹配(包括数组→指针、函数→指针、T → const T)
// 2. 提升匹配(如 char → int)
// 3. 标准转换匹配(如 int → double)
// 4. 用户定义转换匹配

void f(int);
void f(double);
f(3.14f);  // float → double(提升),选 f(double)
f('a');    // char → int(提升),选 f(int)

// 默认实参:只能在声明中指定一次
std::string make_url(const std::string& host,
                     const std::string& path = "/",
                     int port = 80,
                     bool https = false);
auto url = make_url("example.com", "/api", 443, true);
// = "https://example.com:443/api"

5.3 函数指针与 std::function ​

cpp
// C 风格函数指针
using Comparator = bool(*)(int, int);
Comparator cmp = [](int a, int b) { return a < b; }; // 无捕获 lambda 可转换

// C++11 std::function:统一可调用对象
#include <functional>
std::function<bool(int,int)> fn;
fn = [](int a, int b) { return a < b; };     // lambda
fn = std::less<int>{};                       // 函数对象
fn = +[](int a, int b) { return a < b; };    // 无状态 lambda → 函数指针

// 性能考量:std::function 有类型擦除开销(~20-30ns/次调用)
// 热路径上优先使用模板或 auto 接受 lambda
template<typename F>
void sort_with(std::vector<int>& v, F&& cmp) {
    std::sort(v.begin(), v.end(), std::forward<F>(cmp));  // 零开销
}

5.4 constexpr 函数(C++11 → C++14 → C++20) ​

cpp
// C++11: 只能包含一个 return 语句
constexpr int factorial_11(int n) {
    return n <= 1 ? 1 : n * factorial_11(n - 1);
}

// C++14: 允许多条语句
constexpr int factorial_14(int n) {
    int result = 1;
    for (int i = 2; i <= n; ++i) result *= i;
    return result;
}

// C++20: 允许虚函数、try-catch、dynamic_cast 等
constexpr int arr[factorial_14(5)] = {};  // 编译期数组大小
static_assert(factorial_14(5) == 120);

// C++20 consteval:必须在编译期求值
consteval int compile_only(int n) { return n * n; }
int runtime = 5;
// int x = compile_only(runtime);  // ❌ 编译错误:必须常量表达式
constexpr int y = compile_only(5);  // ✅

六、类——面向对象编程(C++ Primer Ch7 + Effective C++ §4) ​

6.1 类的基础设计 ​

cpp
class Person {
public:
    // 构造函数
    Person() = default;                               // 默认构造
    explicit Person(std::string name)                 // explicit 阻止隐式转换
        : name_(std::move(name)), age_(0) {}          // 初始化列表(不是赋值!)
    Person(std::string name, int age)
        : name_(std::move(name)), age_(age) {}

    // 访问器(const 正确性)
    const std::string& name() const { return name_; }
    int age() const { return age_; }

    // 修改器
    void set_age(int a) { age_ = a; }

private:  // Effective C++ §22: 数据成员应声明为 private
    std::string name_;
    int age_ = 0;  // C++11 类内默认初始化
};

Person p1;                    // 默认构造
Person p2("Alice");           // 直接构造
// Person p3 = "Bob";         // ❌ explicit 阻止
Person p3 = Person("Bob");    // ✅ 明确构造

6.2 构造函数进阶:委托、继承与 explicit ​

cpp
// 委托构造函数 (C++11)
class Rectangle {
    int w_, h_;
public:
    Rectangle(int w, int h) : w_(w), h_(h) {}        // 主构造函数
    Rectangle() : Rectangle(0, 0) {}                  // 委托
    explicit Rectangle(int side) : Rectangle(side, side) {} // 正方形
};

// 继承构造函数 (C++11)
class ColoredRect : public Rectangle {
    std::string color_;
public:
    using Rectangle::Rectangle;  // 继承所有基类构造函数
    ColoredRect(int w, int h, std::string c)
        : Rectangle(w, h), color_(std::move(c)) {}
};

6.3 访问控制与友元 ​

cpp
class Widget {
public:     // 任何代码可见
    void public_method();

protected:  // 自身 + 派生类可见
    void protected_method();

private:    // 仅自身 + 友元可见
    int data_;

    friend class WidgetFactory;         // 类友元
    friend void audit(const Widget&);   // 函数友元
    friend void WidgetFactory::make();  // 成员函数友元
};

6.4 static 成员与 inline 变量(C++17) ​

cpp
class Counter {
    static int count_;         // 声明(C++17 前需要外部定义)
    inline static int total_;  // C++17 内联静态成员:声明即定义
public:
    Counter() { ++count_; ++total_; }
    static int count() { return count_; }
};
// int Counter::count_ = 0;    // C++17 前必须的外部定义
int Counter::total_ = 0;       // inline 不需要外部定义

七、拷贝控制与移动语义(C++ Primer Ch13 + Effective C++ §2) ​

7.1 Rule of Three / Five / Zero ​

cpp
// ❌ C++98 Rule of Three:定义了析构/拷贝构造/拷贝赋值的任一个,就必须定义全部三个
// ✅ C++11 Rule of Five:加上移动构造和移动赋值
// 🏆 Rule of Zero:不要自定义这些,让编译器生成

// 编译器自动生成规则(=default / =delete):
class MoveOnly {
    std::unique_ptr<int> ptr_;  // unique_ptr 不可拷贝 → 类也自动不可拷贝
public:
    MoveOnly() = default;                     // 默认构造
    ~MoveOnly() = default;                    // 析构
    MoveOnly(MoveOnly&&) = default;           // 移动构造(自动生成)
    MoveOnly& operator=(MoveOnly&&) = default;// 移动赋值
    MoveOnly(const MoveOnly&) = delete;       // 显式禁止拷贝
    MoveOnly& operator=(const MoveOnly&) = delete;
};

7.2 拷贝控制实战:手写 vector 简化版 ​

cpp
template<typename T>
class SimpleVector {
    T* data_ = nullptr;
    size_t size_ = 0;
    size_t cap_ = 0;

    void reallocate(size_t new_cap) {
        T* new_data = static_cast<T*>(::operator new(new_cap * sizeof(T)));
        for (size_t i = 0; i < size_; ++i)
            new (new_data + i) T(std::move(data_[i]));  // placement new + 移动
        for (size_t i = 0; i < size_; ++i)
            data_[i].~T();
        ::operator delete(data_);
        data_ = new_data;
        cap_ = new_cap;
    }

public:
    SimpleVector() = default;

    // 拷贝构造:深拷贝每个元素
    SimpleVector(const SimpleVector& other) {
        data_ = static_cast<T*>(::operator new(other.cap_ * sizeof(T)));
        size_ = other.size_;
        cap_ = other.cap_;
        std::uninitialized_copy(other.data_, other.data_ + size_, data_);
    }

    // 拷贝赋值:copy-and-swap 惯用法
    SimpleVector& operator=(const SimpleVector& rhs) {
        SimpleVector temp(rhs);  // 拷贝构造
        swap(temp);              // 交换
        return *this;
    }                           // temp 析构,释放旧资源

    // 移动构造:窃取资源
    SimpleVector(SimpleVector&& other) noexcept
        : data_(other.data_), size_(other.size_), cap_(other.cap_) {
        other.data_ = nullptr;
        other.size_ = other.cap_ = 0;  // 确保 other 可安全析构
    }

    // 移动赋值
    SimpleVector& operator=(SimpleVector&& rhs) noexcept {
        if (this != &rhs) {
            clear();
            data_ = rhs.data_;
            size_ = rhs.size_;
            cap_ = rhs.cap_;
            rhs.data_ = nullptr;
            rhs.size_ = rhs.cap_ = 0;
        }
        return *this;
    }

    ~SimpleVector() { clear(); }

    void swap(SimpleVector& other) noexcept {
        std::swap(data_, other.data_);
        std::swap(size_, other.size_);
        std::swap(cap_, other.cap_);
    }

    void push_back(const T& value) {
        if (size_ == cap_) reallocate(cap_ == 0 ? 1 : cap_ * 2);
        new (data_ + size_) T(value);
        ++size_;
    }

    void push_back(T&& value) {
        if (size_ == cap_) reallocate(cap_ == 0 ? 1 : cap_ * 2);
        new (data_ + size_) T(std::move(value));
        ++size_;
    }

    void clear() {
        for (size_t i = 0; i < size_; ++i) data_[i].~T();
        ::operator delete(data_);
        data_ = nullptr;
        size_ = cap_ = 0;
    }

    size_t size() const { return size_; }
    size_t capacity() const { return cap_; }
    T& operator[](size_t i) { return data_[i]; }
    const T& operator[](size_t i) const { return data_[i]; }
};

7.3 移动语义深剖:左值、右值、万能引用 ​

cpp
// 值类别体系(C++ 最复杂的部分之一)
//           expression
//          /          \
//      glvalue        rvalue
//      /     \        /     \
//   lvalue  xvalue  prvalue
//
// lvalue: 有标识、不可移动(变量名、解引用、前置++)
// xvalue: 有标识、可移动(std::move 返回值、返回 T&& 的函数)
// prvalue: 无标识、可移动(字面量、临时对象、后置++)

int x = 42;
int& lref = x;       // lvalue 引用
int&& rref = 42;     // rvalue 引用绑定到临时对象
int&& rref2 = std::move(x); // std::move 将左值转为右值

// std::move 本质:无条件转换
// 实现等价于:
template<typename T>
constexpr std::remove_reference_t<T>&& move(T&& t) noexcept {
    return static_cast<std::remove_reference_t<T>&&>(t);
}
// 注意:move 不移动任何东西!它只是类型转换
// 真正的移动发生在我们写的移动构造函数/赋值运算符里

// std::forward:有条件转换(完美转发)
template<typename T>
void wrapper(T&& arg) {  // T&& 是万能引用(转发引用)
    foo(std::forward<T>(arg));  // T 是左值引用 → 传左值;否则 → 传右值
}

// 引用折叠规则:
// T&  &  → T&
// T&  && → T&
// T&& &  → T&
// T&& && → T&&   ← 仅此产生右值引用
mermaid
flowchart TD
    subgraph ValueCategory["C++ 值类别"]
        EXPR["expression"] --> GLV["glvalue<br/>'has identity'"]
        EXPR --> RVAL["rvalue<br/>'can be moved'"]
        GLV --> LV["lvalue<br/>变量名、*p、++i"]
        GLV --> XV["xvalue<br/>std::move(x)、T{} 成员"]
        RVAL --> XV
        RVAL --> PRV["prvalue<br/>字面量、临时对象、i++"]
    end
    LV -.-|"& 绑定"| LREF["T&"]
    XV -.-|"&& 绑定"| RREF["T&&"]
    PRV -.-|"&& 绑定"| RREF

7.4 智能指针:告别 new/delete ​

cpp
#include <memory>

// === unique_ptr:独占所有权,零开销 ===
{
    auto p = std::make_unique<Widget>(42);  // C++14 推荐
    // auto p2 = p;                          // ❌ 不可拷贝
    auto p2 = std::move(p);                  // ✅ 转移所有权
    // 离开作用域自动 delete
}

// 自定义删除器
auto fd_deleter = [](FILE* f) { if (f) fclose(f); };
std::unique_ptr<FILE, decltype(fd_deleter)> fp(fopen("test.txt", "r"), fd_deleter);

// === shared_ptr:共享所有权,有引用计数开销 ===
{
    auto sp1 = std::make_shared<Widget>();   // 一次分配对象+控制块
    auto sp2 = sp1;                           // 引用计数 +1
    // sp2 离开作用域:计数 -1
    // sp1 离开作用域:计数 0 → 释放
}

// shared_ptr 控制块结构:
// ┌─────────────────────┐
// │ 强引用计数 (use_count) │
// │ 弱引用计数 (weak_count) │
// │ 删除器、分配器        │
// └─────────────────────┘

// === weak_ptr:打破 shared_ptr 循环引用 ===
struct Node {
    std::shared_ptr<Node> next;
    std::weak_ptr<Node> prev;  // 用 weak_ptr 打破循环!
    ~Node() { std::cout << "destroyed\n"; }
};
{
    auto n1 = std::make_shared<Node>();
    auto n2 = std::make_shared<Node>();
    n1->next = n2;
    n2->prev = n1;  // weak_ptr 不增加引用计数
}   // ✅ 两者都能正确析构

// 使用 weak_ptr
if (auto sp = wp.lock()) {  // 原子提升为 shared_ptr
    sp->do_something();
}
mermaid
flowchart LR
    subgraph Unique["unique_ptr"]
        UP["unique_ptr\<T\>"] -->|"独占"| OBJ1["T 对象"]
    end
    subgraph Shared["shared_ptr"]
        SP1["shared_ptr\<T\>"] --> CB["控制块\nref=2"]
        SP2["shared_ptr\<T\>"] --> CB
        CB --> OBJ2["T 对象"]
        WP["weak_ptr\<T\>"] -.->|"不增计数"| CB
    end

7.5 智能指针最佳实践(Effective C++ §13-17) ​

规则说明
用 make_unique/make_shared异常安全 + 一次分配(shared_ptr)
不用裸 new替换为 make_unique<T>(args...)
shared_ptr 做参数时传值调用方可以选择 move 或 copy
返回 unique_ptr 时按值返回编译器自动做 RVO / 隐式 move
工厂函数返回 unique_ptr调用方可决定是否转为 shared_ptr
避免用 shared_ptr 管理 this用 std::enable_shared_from_this

---

## 八、运算符重载与类型转换(C++ Primer Ch14)

### 8.1 运算符重载

```cpp
class Complex {
    double re_, im_;
public:
    Complex(double r = 0, double i = 0) : re_(r), im_(i) {}

    // 复合赋值:成员函数,返回 *this 引用
    Complex& operator+=(const Complex& rhs) {
        re_ += rhs.re_;
        im_ += rhs.im_;
        return *this;
    }

    // 算术运算符:非成员函数,返回新对象
    friend Complex operator+(Complex lhs, const Complex& rhs) {
        lhs += rhs;  // 复用 +=
        return lhs;
    }

    // 三路比较 <=> (C++20) — 自动生成所有比较运算符!
    auto operator<=>(const Complex&) const = default;

    // 下标运算符
    double& operator[](size_t i) { return i == 0 ? re_ : im_; }
    const double& operator[](size_t i) const { return i == 0 ? re_ : im_; }

    // 函数调用运算符(仿函数)
    double operator()(double x) const { return re_ * x + im_; }
};
Complex c(1, 2);
double y = c(3.14);  // c 像函数一样调用

8.2 隐式类型转换 ​

cpp
class Rational {
    int num_, den_;
public:
    // 非 explicit 的单参数构造 → 隐式转换
    Rational(int n = 0, int d = 1) : num_(n), den_(d) {}

    // 隐式转换为 double
    operator double() const { return static_cast<double>(num_) / den_; }
};

Rational r = 5;      // 5 → Rational(5, 1)
double d = r;        // r → double
// 风险:意外发生时双重隐式转换
// 准则:单参数构造函数加 explicit,类型转换运算符加 explicit (C++11)

---

## 九、模板与泛型编程(C++ Primer Ch16 + Effective C++ §7)

### 9.1 函数模板基础

```cpp
// 模板参数推导(C++17 类模板参数推导 → 见下文)
template<typename T>
T max(T a, T b) {
    return a > b ? a : b;
}

int i = max(3, 5);          // T = int
double d = max(3.14, 2.72); // T = double
// max(3, 3.14);             // ❌ 推导冲突 int vs double
max<double>(3, 3.14);        // ✅ 显式指定 T = double,3 隐式转 double

// 非类型模板参数 (NTTP)
template<typename T, size_t N>
constexpr size_t array_size(T (&)[N]) { return N; }

int arr[10];
static_assert(array_size(arr) == 10);

// C++20: 类类型 NTTP + auto NTTP
template<auto N>  // auto 推导非类型参数
struct FixedString {
    char data[N + 1]{};
    constexpr FixedString(const char (&s)[N + 1]) {
        std::copy_n(s, N + 1, data);
    }
};

9.2 类模板 ​

cpp
template<typename T>
class Stack {
    std::vector<T> elems_;
public:
    void push(const T& elem) { elems_.push_back(elem); }
    void push(T&& elem) { elems_.push_back(std::move(elem)); }
    std::optional<T> pop() {
        if (elems_.empty()) return std::nullopt;
        T top = std::move(elems_.back());
        elems_.pop_back();
        return top;
    }
};

Stack<int> int_stack;  // 必须显式指定类型
// C++17 CTAD:类模板参数推导
Stack int_stack2 = Stack{1, 2, 3};  // 推导为 Stack<int>

// 用户自定义推导指引
template<typename T>
Stack(T) -> Stack<T>;

9.3 变参模板(C++11) ​

cpp
// 递归终止
void print() { std::cout << "\n"; }

// 变参模板 + 折叠表达式 (C++17)
template<typename T, typename... Args>
void print(T&& first, Args&&... args) {
    std::cout << std::forward<T>(first);
    if constexpr (sizeof...(args) > 0) std::cout << ", ";
    print(std::forward<Args>(args)...);
}

print(42, "hello", 3.14, 'x');  // 42, hello, 3.14, x

// C++17 折叠表达式:一行展开
template<typename... Args>
auto sum(Args... args) { return (args + ...); }  // 一元右折叠

template<typename... Args>
auto all_true(Args... args) { return (... && args); }  // 一元左折叠

// 编译期输出所有参数类型
template<typename... Args>
void print_types() {
    ((std::cout << typeid(Args).name() << " "), ...);
}

9.4 Concepts(C++20 核心特性) ​

cpp
#include <concepts>

// 定义概念:约束模板参数
template<typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;

// 使用概念(四种语法)
// 方式 1: requires 子句
template<typename T> requires Numeric<T>
T add(T a, T b) { return a + b; }

// 方式 2: 概念代替 typename
template<Numeric T>
T multiply(T a, T b) { return a * b; }

// 方式 3: 简写函数模板 (C++20)
auto divide(Numeric auto a, Numeric auto b) { return a / b; }

// 方式 4: requires 表达式(最灵活)
template<typename T>
concept Sortable = requires(T a, T b) {
    { a < b } -> std::convertible_to<bool>;  // 复合要求
    std::swap(a, b);                          // 简单要求
    requires std::destructible<T>;            // 嵌套要求
};

// 使用示例
// add("hello", "world");    // ❌ 约束失败:const char* 不满足 Numeric
add(3, 5);                   // ✅

// 编译器错误信息对比:
// 无概念:"no match for 'operator+' in ..." [200行模板展开错误]
// 有概念:"constraints not satisfied: 'Numeric<const char*>' evaluated to false"
mermaid
flowchart TD
    subgraph WithoutConcepts["C++17 之前"]
        A["模板调用"] --> B["SFINAE / 标签分发"]
        B --> C{"匹配?"}
        C -->|否| D["数百行模板展开错误<br/>难以阅读"]
        C -->|是| E["编译成功"]
    end
    subgraph WithConcepts["C++20 Concepts"]
        F["模板调用"] --> G["概念检查"]
        G --> H{"满足约束?"}
        H -->|否| I["简洁错误:constraint not satisfied<br/>Numeric\<const char*\> evaluated to false"]
        H -->|是| J["编译成功"]
    end

9.5 模板元编程与现代替代方案 ​

cpp
// C++11 编译期判断类型
template<typename T>
struct is_pointer : std::false_type {};

template<typename T>
struct is_pointer<T*> : std::true_type {};

template<typename T>
constexpr bool is_pointer_v = is_pointer<T>::value;  // C++17 变量模板

static_assert(is_pointer_v<int*>);
static_assert(!is_pointer_v<int>);

// if constexpr (C++17):替代 SFINAE 和标签分发的首选方式
template<typename T>
auto process(T value) {
    if constexpr (std::is_integral_v<T>) {
        return value * 2;        // 仅对整数类型编译
    } else if constexpr (std::is_floating_point_v<T>) {
        return value * 3.14;     // 仅对浮点类型编译
    } else {
        static_assert(sizeof(T) == 0, "unsupported type");  // C++23 前需要此技巧
    }
}

十、STL 容器全景(C++11/17/20/23) ​

10.1 容器选择决策树 ​

mermaid
flowchart TD
    START["需要存储元素"] --> Q1{"需要 O(1) 随机访问?"}
    Q1 -->|是| Q2{"需要头尾快速插入?"}
    Q2 -->|头尾| DQ["std::deque"]
    Q2 -->|仅尾部| VEC["std::vector(默认选择)"]
    Q2 -->|固定大小| ARR["std::array"]
    Q1 -->|否| Q3{"需要排序/唯一键?"}
    Q3 -->|有序| Q4{"key-value?"}
    Q4 -->|是| MAP["std::map(红黑树)"]
    Q4 -->|否| SET["std::set(红黑树)"]
    Q3 -->|无序| Q5{"key-value?"}
    Q5 -->|是| UMAP["std::unordered_map(哈希表)"]
    Q5 -->|否| USET["std::unordered_set(哈希表)"]
    Q3 -->|仅遍历| Q6{"需要快速插入?"}
    Q6 -->|是| LIST["std::list(双向链表)"]
    Q6 -->|前向| FLIST["std::forward_list"]

10.2 容器复杂度速查 ​

操作vectordequelistmapunordered_map
随机访问O(1)O(1)—O(log n)O(1) avg
头插入—O(1)O(1)O(log n)O(1) avg
尾插入O(1)*O(1)O(1)O(log n)O(1) avg
中间插入O(n)O(n)O(1)**O(log n)O(1) avg
查找O(n)O(n)O(n)O(log n)O(1) avg
迭代器失效重新分配时中间插入时永不永不重新哈希时

* 均摊 O(1),扩容时会拷贝。 ** 需要已有迭代器。

10.3 C++11/17/20 新增容器 ​

cpp
// C++11: 无序关联容器
std::unordered_map<std::string, int> umap;
std::unordered_set<int> uset;
std::unordered_multimap<std::string, int> ummap;
std::unordered_multiset<int> umset;

// C++11: 定长数组(替代 C 数组)
std::array<int, 5> arr = {1, 2, 3, 4, 5};  // 不退化,有 .size()
std::forward_list<int> fl;  // 单向链表,比 list 更省内存

// C++17: 容器辅助
std::map<std::string, int> m;
// 插入或访问(C++17 try_emplace / insert_or_assign)
m.try_emplace("key", 42);     // 只有不存在时才构造
m.insert_or_assign("key", 99); // 存在则赋值,不存在则插入

// C++17: 节点操作(std::map ⇄ std::unordered_map 之间移动节点)
auto node = m.extract("key");                 // 仅移动节点,不拷贝值
std::unordered_map<std::string, int> um;
um.insert(std::move(node));

// C++17: string_view(零拷贝只读字符串视图)
std::string_view sv = "hello";
auto sub = sv.substr(1, 3);  // O(1)!不分配内存

// C++17: std::optional(可能无值的包装)
std::optional<int> find_even(const std::vector<int>& v) {
    for (int x : v) if (x % 2 == 0) return x;
    return std::nullopt;
}

// C++17: std::variant(类型安全的联合体)
std::variant<int, std::string, double> v;
v = 42;                        // holds int
v = "hello";                   // holds string
std::visit([](auto&& arg) {    // 访问当前值
    std::cout << arg << "\n";
}, v);

// C++20: std::span(不拥有所有权,连续内存视图)
void process(std::span<int> data) {  // 接受 vector/array/C数组
    for (int& x : data) x *= 2;
}
std::vector<int> vec = {1, 2, 3};
process(vec);  // ✅ 无需指针+大小对

// C++20: contains() 成员函数
if (m.contains("key")) { /* ... */ }
if (set.contains(42)) { /* ... */ }

// C++23: flat_map / flat_set(基于排序 vector,缓存友好)
#include <flat_map>  // C++23,部分编译器支持
std::flat_map<int, std::string> fm;
// 底层是 pair<K,V> 的排序数组,二分查找 O(log n)
// 内存连续性使其在大数据量时比 std::map 快 2-5 倍

10.4 std::vector<bool> 陷阱与 std::bitset ​

cpp
// vector<bool> 是特化:每个元素占 1 bit,返回代理对象
std::vector<bool> vb = {true, false, true};
// auto& r = vb[0];         // ❌ 编译错误:返回的不是 bool&
auto r = vb[0];              // ✅ 按值接收
vb.flip();                   // 全部取反

// 替代方案:std::bitset<N>(固定大小)、std::vector<char>(动态大小)
std::bitset<64> flags;
flags.set(3);                // 第 3 位置 1
flags.test(3);               // true
bool all_set = flags.all();  // C++11

---

## 十一、并发编程(C++11/17/20)

### 11.1 线程基础

```cpp
#include <thread>
#include <mutex>
#include <condition_variable>
#include <future>
#include <atomic>

// 创建线程
void worker(int id, const std::string& msg) {
    std::cout << "Thread " << id << ": " << msg << "\n";
}

std::thread t1(worker, 1, "hello");  // 参数按值传递
std::thread t2([](int x) {           // lambda
    std::cout << x * x << "\n";
}, 42);

t1.join();   // 等待线程结束
t2.detach(); // 分离线程(后台运行,不等待)

11.2 std::mutex 与 RAII 锁 ​

cpp
// 互斥锁 + lock_guard(最常用的组合)
std::mutex mtx;
int shared_counter = 0;

void increment() {
    std::lock_guard<std::mutex> lock(mtx);  // RAII:构造加锁,析构解锁
    ++shared_counter;
}

// C++17 scoped_lock:同时锁多个 mutex(避免死锁!)
std::mutex m1, m2;

void transfer() {
    // ❌ 死锁风险:A 锁 m1→m2,B 锁 m2→m1
    // std::lock_guard lk1(m1);
    // std::lock_guard lk2(m2);

    // ✅ scoped_lock 使用 std::lock 算法避免死锁
    std::scoped_lock lock(m1, m2);
}

// C++14 shared_mutex:读写锁
std::shared_mutex rw_mtx;
int data = 0;

int read() {
    std::shared_lock lock(rw_mtx);   // 多个读者并发
    return data;
}

void write(int val) {
    std::unique_lock lock(rw_mtx);   // 写者独占
    data = val;
}

11.3 std::condition_variable ​

cpp
// 生产者-消费者
std::mutex mtx;
std::condition_variable cv;
std::queue<int> q;
bool done = false;

void producer() {
    for (int i = 0; i < 10; ++i) {
        {
            std::lock_guard lk(mtx);
            q.push(i);
        }
        cv.notify_one();  // 通知消费者
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
    {
        std::lock_guard lk(mtx);
        done = true;
    }
    cv.notify_all();
}

void consumer() {
    while (true) {
        std::unique_lock lk(mtx);
        cv.wait(lk, []{ return !q.empty() || done; });  // 等待+条件检查
        if (done && q.empty()) break;
        int val = q.front(); q.pop();
        lk.unlock();  // 处理时释放锁
        std::cout << "consumed: " << val << "\n";
    }
}

11.4 std::atomic:无锁编程 ​

cpp
#include <atomic>

std::atomic<int> counter{0};
std::atomic<bool> ready{false};
std::atomic<int*> ptr{nullptr};

// 原子操作
counter.fetch_add(1, std::memory_order_relaxed);  // counter++
int old = counter.exchange(42);                     // 交换并返回旧值

// CAS(Compare-And-Swap)循环:无锁数据结构的基础
int expected = 0;
if (counter.compare_exchange_strong(expected, 1)) {
    // 成功:counter 从 0 变成了 1
}

// 内存序(从弱到强):
// relaxed → acquire/release → sequentially consistent (默认)
// 规则:无锁编程时,至少用 acquire-release;不确定就用默认 seq_cst

// 实战:无锁单生产者单消费者队列
template<typename T, size_t N>
class SPSCQueue {
    std::array<T, N> buffer_;
    std::atomic<size_t> write_pos_{0};
    std::atomic<size_t> read_pos_{0};
    static constexpr size_t MASK = N - 1;  // N 必须是 2 的幂
public:
    bool try_push(const T& item) {
        size_t w = write_pos_.load(std::memory_order_relaxed);
        size_t r = read_pos_.load(std::memory_order_acquire);
        if (w - r >= N) return false;  // 满
        buffer_[w & MASK] = item;
        write_pos_.store(w + 1, std::memory_order_release);
        return true;
    }
    bool try_pop(T& item) {
        size_t r = read_pos_.load(std::memory_order_relaxed);
        size_t w = write_pos_.load(std::memory_order_acquire);
        if (r == w) return false;  // 空
        item = buffer_[r & MASK];
        read_pos_.store(r + 1, std::memory_order_release);
        return true;
    }
};

11.5 std::future 与 std::async ​

cpp
#include <future>

// async:异步执行函数,返回 future
auto fut = std::async(std::launch::async, [] {
    std::this_thread::sleep_for(std::chrono::seconds(1));
    return 42;
});
// 主线程可以做其他事...
int result = fut.get();  // 阻塞直到结果就绪,只可调用一次

// promise-future 配对
std::promise<int> prom;
std::future<int> fut = prom.get_future();
std::thread([&prom] {
    prom.set_value(42);  // 设置结果
}).detach();
int val = fut.get();

// C++20: std::jthread(自动 join + 可中断)
#include <thread>
std::jthread jt([](std::stop_token token) {
    while (!token.stop_requested()) {
        // 工作...
    }
});
// jt.request_stop();  // 请求停止
// 析构时自动 join
mermaid
flowchart LR
    subgraph Producer["生产者线程"]
        PT["promise"]
        PT -->|"set_value(42)"| SF["共享状态"]
    end
    subgraph Consumer["消费者线程"]
        FT["future"] -->|"get()"| SF
    end
    SF -->|"传递结果"| RESULT["42"]

十二、Effective C++ 核心条款精选 ​

12.1 让自己习惯 C++ ​

条款核心要点
§1: C++ 是语言联邦C、Object-Oriented C++、Template C++、STL 四个子语言,规则不同
§2: 用 const/enum/inline 替代 #define编译期类型检查优于预处理期文本替换
§3: 尽可能用 constconst 成员函数、const 迭代器、const 参数——编译器帮你找 bug
§4: 确保对象使用前已初始化构造函数用初始化列表,局部 static 用函数包装
cpp
// §4 实战:跨编译单元初始化顺序问题
// ❌ 危险的全局对象
// file1.cpp: extern Database db;
// file2.cpp: Logger log;  // 构造时可能用到 db(未初始化!)

// ✅ Meyers Singleton:保证首次使用时初始化
Database& get_db() {
    static Database db("config.ini");  // C++11 保证线程安全初始化
    return db;
}

12.2 构造 / 析构 / 赋值 ​

条款核心要点
§5: 编译器默认生成的函数默认构造、拷贝构造、拷贝赋值、析构、移动(C++11)、移动赋值(C++11)
§6: 拒绝编译器生成的函数= delete (C++11) 替代 private + 不实现
§7: 多态基类声明 virtual 析构否则 delete 基类指针导致未定义行为
§8: 析构函数不抛异常C++11 起析构默认 noexcept
§9: 构造/析构不调用 virtual 函数此时对象的动态类型是当前类,不是派生类
*§10: operator= 返回 this支持链式赋值:a = b = c
§11: operator= 处理自赋值copy-and-swap 法天然处理自赋值
§12: 拷贝函数应拷贝所有成员新增成员时容易忘记更新拷贝函数

12.3 资源管理 ​

条款核心要点
§13: 用对象管理资源RAII:构造函数获取,析构函数释放
§14: 注意资源管理类的拷贝行为禁止拷贝、引用计数、深拷贝、转移所有权
§15: 在资源管理类中提供对原始资源的访问.get() 显式转换,不要隐式转换(指针除外)
§16: new/delete 配对new ↔ delete,new[] ↔ delete[]
§17: 独立语句中初始化智能指针process(std::shared_ptr<Widget>(new Widget), foo()) 可能泄漏

12.4 设计与声明 ​

条款核心要点
§18: 让接口易于正确使用类型系统约束、工厂函数返回智能指针
§20: 传 const 引用而非传值C++11 后对"需持有副本"的参数可用传值+move
§23: 非成员非友元函数优于成员函数减少封装破坏,用命名空间组织
§24: 所有参数都需类型转换时用非成员operator* 应为非成员以支持 2 * r
§25: 不抛异常的 swap特化 std::swap 或提供成员 swap

### 12.5 实现

| 条款 | 核心要点 |
|------|---------|
| **§26: 尽量延后变量定义** | 定义时即初始化,避免不必要的构造/析构 |
| **§27: 减少转型** | 优先 C++ 风格转型,避免 `dynamic_cast` 在热路径 |
| **§28: 避免返回 handles 指向对象内部** | 返回引用/指针/迭代器暴露内部 → 破坏封装 |
| **§29: 为"异常安全"努力是值得的** | 至少基本保证,追求强保证 |
| **§30: 透彻了解 inline** | inline 是建议而非强制;编译器自主决定;影响 ABI |

### 12.6 继承与面向对象设计

| 条款 | 核心要点 |
|------|---------|
| **§32: public 继承 = is-a** | 派生类对象可以无条件当基类使用(LSP) |
| **§33: 避免遮掩继承而来的名称** | 用 `using Base::name;` 引入基类重载 |
| **§34: 区分接口继承和实现继承** | 纯虚 = 仅继承接口;非纯虚 = 接口+默认实现;非虚 = 接口+强制实现 |
| **§35: 考虑 virtual 的替代方案** | NVI 模板方法、策略模式、`std::function` 策略 |
| **§36: 绝不重定义继承的非虚函数** | 静态绑定 vs 动态绑定导致不一致 |
| **§37: 绝不重定义继承的默认参数值** | 默认参数静态绑定,虚函数动态绑定 → 混乱 |
| **§38: 组合优于私有继承** | has-a 用组合,is-implemented-in-terms-of 可用私有继承 |
| **§39: 明智使用 private 继承** | 空的基类优化(EBO)是其少数合理用途之一 |

---

## 十三、编译器优化实战

### 13.1 编译器优化列表

```bash
# GCC/Clang 优化级别
-O0  # 无优化,调试用
-O1  # 基本优化(不影响编译时间)
-O2  # 标准优化(推荐生产用,严格遵循标准)
-O3  # 激进优化(循环展开、函数内联更激进,可能增大二进制体积)
-Os  # 优化体积
-Oz  # Clang 特有:更激进的体积优化
-Ofast # 不严格遵循标准(如 -ffast-math),非通用代码慎用
cpp
// 具体优化技术例析

// 1. RVO / NRVO(返回值优化):C++17 强制保证
std::vector<int> create_vector() {
    return std::vector<int>{1, 2, 3, 4, 5};  // 不拷贝!直接在调用方栈上构造
}
std::vector<int> v = create_vector();  // 零拷贝

// 2. 拷贝消除(Copy Elision):C++17 起在特定场景强制
struct NonCopyable {
    NonCopyable() = default;
    NonCopyable(const NonCopyable&) = delete;
};
NonCopyable f() { return NonCopyable{}; }  // C++17 合法!不触发拷贝构造

// 3. 内联优化
inline int square(int x) { return x * x; }
// 编译后可能变成:int y = 5 * 5;(常数折叠后:int y = 25;)

// 4. 死代码消除 (DCE)
void dead_code() {
    int x = 42;
    // x 从未被使用 → 编译器删除整个赋值
}

// 5. 循环优化
void loop_opt() {
    // 原代码
    for (int i = 0; i < 100; ++i)
        arr[i] = i * 2 + 1;

    // 编译器可能变换为(循环展开 / 向量化):
    // for (int i = 0; i < 100; i += 4) {
    //     arr[i:i+4] = SIMD(i*2+1, (i+1)*2+1, (i+2)*2+1, (i+3)*2+1);
    // }
}

13.2 现代 C++ 性能利器 ​

cpp
// 1. std::string_view:零拷贝字符串操作(C++17)
void process_log(std::string_view line) {
    auto level = line.substr(1, 4);     // O(1)!
    auto msg = line.substr(7);           // O(1)!
    if (level == "INFO") { /* ... */ }
}

// 2. std::span:安全数组视图(C++20)
float dot_product(std::span<const float> a, std::span<const float> b) {
    float sum = 0;
    for (size_t i = 0; i < a.size(); ++i)
        sum += a[i] * b[i];
    return sum;
}

// 3. 移动语义消除深拷贝
std::string s1 = "very long string ...";
std::string s2 = std::move(s1);  // O(1):仅复制指针和长度
// s1 现在为空(valid but unspecified state)

// 4. emplace 系列:原地构造
std::vector<std::pair<int, std::string>> items;
items.emplace_back(42, "hello");  // 直接在 vector 内存中构造 pair

// 5. C++20 ranges:惰性求值管道
#include <ranges>
auto pipeline = data
    | std::views::filter([](int x) { return x > 0; })
    | std::views::transform([](int x) { return x * 2; })
    | std::views::take(5);
// 直到 begin() 调用才开始计算,且只计算需要的元素

13.3 编译器诊断工具 ​

bash
# Sanitizers(Clang/GCC 都支持)
-fsanitize=address     # ASan:内存错误(越界、use-after-free、double-free)
-fsanitize=thread      # TSan:数据竞争检测
-fsanitize=undefined   # UBSan:未定义行为(除零、溢出、空指针解引用)
-fsanitize=memory      # MSan(仅 Clang Linux):未初始化内存读
-fsanitize=leak        # LSan:内存泄漏

# 使用示例
clang++ -std=c++23 -fsanitize=address,undefined -g -O1 test.cpp -o test
./test  # 运行时会报告内存错误和未定义行为

# 性能分析
perf record ./program           # 采样 CPU 性能计数器
perf report                     # 查看热点函数
perf stat ./program             # 查看 CPI、缓存命中率等

13.4 编译器输出分析 ​

bash
# 查看编译器生成的汇编
g++ -S -O2 -std=c++23 main.cpp -o main.s
clang++ -S -O2 -std=c++23 -emit-llvm main.cpp -o main.ll  # LLVM IR

# 查看优化报告 (GCC)
g++ -O2 -std=c++23 -fopt-info-vec main.cpp    # 向量化报告
g++ -O2 -std=c++23 -fopt-info-inline main.cpp # 内联报告

# 查看优化报告 (Clang)
clang++ -O2 -std=c++23 -Rpass=loop-vectorize main.cpp
clang++ -O2 -std=c++23 -Rpass-missed=.* main.cpp  # 未成功的优化

十四、C++17/20/23 新特性速查 ​

14.1 C++17 核心新特性 ​

特性语法说明
结构化绑定auto [x, y] = pair;解包 pair/tuple/struct
if constexprif constexpr (cond)编译期分支
if/switch 初始化器if (auto v = f(); v)限制变量作用域
折叠表达式(args + ...)变参模板简化
string_viewstd::string_view零拷贝只读字符串
optional/variant/anystd::optional<T>安全替代空指针/联合体
类模板参数推导auto p = std::pair{1, 2.0}CTAD
内联变量inline static int x = 1;头文件定义变量
文件系统std::filesystem跨平台文件操作
并行算法std::sort(std::execution::par, ...)自动并行化

14.2 C++20 核心新特性 ​

特性语法说明
Conceptstemplate<std::integral T>约束模板参数
Rangesviews::filter | views::transform惰性管道
Coroutinesco_await/co_yield/co_return异步编程
Modulesimport std;替代 #include
三路比较auto operator<=>(const T&) = default自动生成所有比较
spanstd::span<T>不拥有所有权的连续视图
日历/时区std::chrono::year_month_day标准库时间
formatstd::format("{} + {} = {}", 1, 2, 3)类型安全格式化
source_locationstd::source_location::current()替代 __FILE__/__LINE__
constexpr 增强constexpr virtual, constexpr try-catch更广编译期计算
jthreadstd::jthread自动 join + 可中断
likely/unlikelyif (x) [[likely]]分支预测提示
bit_caststd::bit_cast<double>(u64)类型双关替代品

14.3 C++23 关键新特性 ​

特性说明
std::expected<T,E>类似 Rust Result:成功返回值或错误
std::flat_map/std::flat_set排序数组实现的关联容器
ranges 增强views::zip、views::chunk、views::slide
deducing this成员函数推导对象类型(简化 CRTP)
if consteval编译期分支检测
std::print / std::println更快的格式化输出
多维下标 operator[]matrix[1, 2] 语法
Monadic operations for optionalopt.and_then(f).or_else(g).transform(h)
std::generator基于协程的生成器

14.4 C++26 展望 ​

特性状态说明
反射 (Reflection)TS编译期自省:^^T 获取类型元信息
ContractsTS前置/后置/断言条件检查
std::executionTS调度器+发送器+接收器的统一异步模型
Pattern Matching草案inspect (expr) { pattern => action; }
std::simd实验可移植 SIMD 类型

十五、练习代码:从 C 到现代 C++ ​

练习 1:RAII 文件包装器 ​

cpp
#include <cstdio>
#include <stdexcept>
#include <string>

class File {
    FILE* handle_;
public:
    explicit File(const char* path, const char* mode) {
        handle_ = std::fopen(path, mode);
        if (!handle_) throw std::runtime_error("Failed to open: " + std::string(path));
    }

    ~File() { if (handle_) std::fclose(handle_); }

    // 禁止拷贝,允许移动
    File(const File&) = delete;
    File& operator=(const File&) = delete;
    File(File&& other) noexcept : handle_(other.handle_) { other.handle_ = nullptr; }
    File& operator=(File&& other) noexcept {
        if (this != &other) {
            if (handle_) std::fclose(handle_);
            handle_ = other.handle_;
            other.handle_ = nullptr;
        }
        return *this;
    }

    std::string read_all() {
        std::fseek(handle_, 0, SEEK_END);
        long size = std::ftell(handle_);
        std::fseek(handle_, 0, SEEK_SET);
        std::string content(size, '\0');
        std::fread(content.data(), 1, size, handle_);
        return content;
    }

    FILE* get() const { return handle_; }
};

// 使用
int main() {
    File f("test.txt", "r");
    std::string content = f.read_all();
    std::cout << content;
}  // f 自动关闭

练习 2:线程安全单例 ​

cpp
#include <mutex>
#include <memory>

// C++11 起:local static 初始化是线程安全的
class Config {
    std::map<std::string, std::string> data_;
    Config() = default;  // 私有构造
public:
    Config(const Config&) = delete;
    Config& operator=(const Config&) = delete;

    static Config& instance() {
        static Config inst;  // C++11 保证线程安全初始化
        return inst;
    }

    std::string get(const std::string& key) const {
        auto it = data_.find(key);
        return it != data_.end() ? it->second : "";
    }
    void set(const std::string& key, std::string value) {
        std::lock_guard lk(mutex_);
        data_[key] = std::move(value);
    }

private:
    mutable std::mutex mutex_;
};

练习 3:变参 printf 替代品(类型安全格式化) ​

cpp
#include <format>  // C++20
#include <iostream>

template<typename... Args>
void safe_print(std::format_string<Args...> fmt, Args&&... args) {
    std::cout << std::format(fmt, std::forward<Args>(args)...);
}

// C++23: 更简单
// import std;
// std::println("{} + {} = {}", 1, 2, 3);

int main() {
    safe_print("Hello {}! You have {} messages.\n", "Alice", 5);
    safe_print("π ≈ {:.6f}\n", 3.1415926535);
}

练习 4:基于 Concepts 的泛型数学库 ​

cpp
#include <concepts>
#include <cmath>

template<typename T>
concept Arithmetic = std::integral<T> || std::floating_point<T>;

template<Arithmetic T>
class Vector2D {
    T x_, y_;
public:
    constexpr Vector2D(T x = 0, T y = 0) : x_(x), y_(y) {}

    constexpr T dot(const Vector2D& other) const {
        return x_ * other.x_ + y_ * other.y_;
    }

    constexpr auto length() const {
        return std::sqrt(dot(*this));  // 对整数返回 double
    }

    constexpr Vector2D operator+(const Vector2D& rhs) const {
        return {x_ + rhs.x_, y_ + rhs.y_};
    }

    // C++20 三路比较:自动生成 == != < <= > >=
    auto operator<=>(const Vector2D&) const = default;
};

static_assert(Vector2D(3, 4).dot(Vector2D(1, 0)) == 3);
static_assert(std::abs(Vector2D(3, 4).length() - 5.0) < 0.001);

练习 5:协程生成器(C++20) ​

cpp
#include <coroutine>
#include <iostream>
#include <optional>

// 简易生成器
template<typename T>
struct Generator {
    struct promise_type {
        T current_value;
        std::suspend_always yield_value(T value) {
            current_value = value;
            return {};
        }
        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        Generator get_return_object() {
            return Generator{std::coroutine_handle<promise_type>::from_promise(*this)};
        }
        void return_void() {}
        void unhandled_exception() { std::terminate(); }
    };

    std::coroutine_handle<promise_type> coro;
    explicit Generator(std::coroutine_handle<promise_type> h) : coro(h) {}
    ~Generator() { if (coro) coro.destroy(); }
    Generator(const Generator&) = delete;
    Generator& operator=(const Generator&) = delete;
    Generator(Generator&& other) noexcept : coro(other.coro) { other.coro = nullptr; }

    std::optional<T> next() {
        if (!coro || coro.done()) return std::nullopt;
        coro.resume();
        if (coro.done()) return std::nullopt;
        return coro.promise().current_value;
    }
};

Generator<int> fibonacci() {
    int a = 0, b = 1;
    while (true) {
        co_yield a;
        int tmp = a + b;
        a = b;
        b = tmp;
    }
}

int main() {
    auto gen = fibonacci();
    for (int i = 0; i < 10; ++i)
        std::cout << *gen.next() << " ";  // 0 1 1 2 3 5 8 13 21 34
}

附录 A:推荐阅读路线 ​

mermaid
flowchart TD
    START["开始学习 C++"] --> PRIMER["C++ Primer (5th) 通读<br/>掌握语法、STL、OOP"]
    PRIMER --> EFFECTIVE["Effective C++ (55条款)<br/>纠正错误认知,养成好习惯"]
    EFFECTIVE --> MODERN["Effective Modern C++<br/>深入 C++11/14 新特性"]
    MODERN --> TEMPLATE["C++ Templates (2nd)<br/>模板元编程精髓"]
    TEMPLATE --> CONCURRENCY["C++ Concurrency in Action<br/>多线程与内存模型"]
    CONCURRENCY --> STROUSTRUP["The C++ Programming Language (4th)<br/>Bjarne 的语言全览"]
    STROUSTRUP --> CPPREF["https://en.cppreference.com<br/>日常查阅标准库"]
    CPPREF --> TALKS["CppCon / C++Now 演讲<br/>跟进最新发展"]

附录 B:编译器速查 ​

bash
# 确认编译器版本和 C++23 支持
g++ --version
g++ -std=c++23 -dM -E -x c++ /dev/null | grep __cplusplus
# __cplusplus 202302L 表示 C++23

# 编译常用完整命令
g++ -std=c++23 -O2 -Wall -Wextra -Wpedantic \
    -fsanitize=address,undefined -g \
    -o program main.cpp

# 使用 Clang 的工具链
clang++ -std=c++23 -O2 \
    -Rpass=loop-vectorize \       # 优化报告
    -fsanitize=address,undefined \
    -fuse-ld=lld \                # 使用 lld 链接器(更快)
    -o program main.cpp
批注模式

💬 文章评论

暂无评论,来说点什么吧 👇

编程学习笔记