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 三大编译器对比
| 特性 | GCC | Clang/LLVM | MSVC |
|---|---|---|---|
| 前端 | cc1plus(自研) | Clang(模块化设计) | C1XX |
| 后端 | GCC 后端(RTL) | LLVM IR → 多目标 | 自研 |
| 优化 | 激进/有时过于激进 | 保守/更安全 | 中等 |
| 诊断 | 可读性一般 | 最强(彩色箭头指向错误位置) | 中等 |
| C++23 支持 | ✅ 大部分 | ✅ 大部分 | ⚠️ 部分 |
| modules 支持 | 实验性(14+) | ✅ (17+) | ✅ (VS2022 17.5+) |
| sanitizers | ASan/TSan/UBSan | ASan/TSan/UBSan/MSan | ASan |
| 工具链 | gdb | lldb + lld | WinDbg |
| 编译命令 | g++ -std=c++23 -O2 | clang++ -std=c++23 -O2 | cl /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位) | 范围 | 字面量后缀 |
|---|---|---|---|
bool | 1 | true/false | — |
char | 1 | -128~127 或 0~255 | 'a' |
wchar_t | 4 | 实现定义 | L'a' |
char8_t(C++20) | 1 | UTF-8 | u8'a' |
char16_t(C++11) | 2 | UTF-16 | u'a' |
char32_t(C++11) | 4 | UTF-32 | U'a' |
short | 2 | -32768~32767 | — |
int | 4 | ±2.1×10⁹ | 42 |
long | 8 | ±9.2×10¹⁸ | 42L |
long long(C++11) | 8 | ±9.2×10¹⁸ | 42LL |
float | 4 | ±3.4×10³⁸, ~7位 | 3.14f |
double | 8 | ±1.7×10³⁰⁸, ~15位 | 3.14 |
long double | 16 | 扩展精度 | 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-072.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++203.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 -.-|"&& 绑定"| RREF7.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
end7.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["编译成功"]
end9.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 容器复杂度速查
| 操作 | vector | deque | list | map | unordered_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(); // 请求停止
// 析构时自动 joinmermaid
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: 尽可能用 const | const 成员函数、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 constexpr | if constexpr (cond) | 编译期分支 |
| if/switch 初始化器 | if (auto v = f(); v) | 限制变量作用域 |
| 折叠表达式 | (args + ...) | 变参模板简化 |
| string_view | std::string_view | 零拷贝只读字符串 |
| optional/variant/any | std::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 核心新特性
| 特性 | 语法 | 说明 |
|---|---|---|
| Concepts | template<std::integral T> | 约束模板参数 |
| Ranges | views::filter | views::transform | 惰性管道 |
| Coroutines | co_await/co_yield/co_return | 异步编程 |
| Modules | import std; | 替代 #include |
| 三路比较 | auto operator<=>(const T&) = default | 自动生成所有比较 |
| span | std::span<T> | 不拥有所有权的连续视图 |
| 日历/时区 | std::chrono::year_month_day | 标准库时间 |
| format | std::format("{} + {} = {}", 1, 2, 3) | 类型安全格式化 |
| source_location | std::source_location::current() | 替代 __FILE__/__LINE__ |
| constexpr 增强 | constexpr virtual, constexpr try-catch | 更广编译期计算 |
| jthread | std::jthread | 自动 join + 可中断 |
| likely/unlikely | if (x) [[likely]] | 分支预测提示 |
| bit_cast | std::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 optional | opt.and_then(f).or_else(g).transform(h) |
std::generator | 基于协程的生成器 |
14.4 C++26 展望
| 特性 | 状态 | 说明 |
|---|---|---|
| 反射 (Reflection) | TS | 编译期自省:^^T 获取类型元信息 |
| Contracts | TS | 前置/后置/断言条件检查 |
std::execution | TS | 调度器+发送器+接收器的统一异步模型 |
| 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
登录后即可发表评论 👇