堆、栈、RAII、容器
delete
一个空指针是合法的,但不能多次delete
;- 手写智能指针:先实现一个
auto_ptr
(赋值为移动语义),再定义引用计数类;每个智能指针类包含两个类的指针:引用技术类和对象类; - 异常安全的赋值函数:复制并交换;
1 | T& operator= (T rhs) { |
vector
保证强异常安全性,如果没有noexcept
的移动构造函数,会调用拷贝构造函数;queue
和stack
依赖现有容器,因此称为容器适配器,默认均为deque
实现;less
和greater
均为通过重载同名结构体的bool operator() (const T& x, const T& y)
得到的函数对象;它们继承的binary_function
和unary_function
已在 C++11 被废弃;
右值引用和移动语义
- 生命周期延长:若一个 prvalue 被绑定到一个引用上,其生命周期会被延长至引用变量的生命周期;
- C++11 后的移动语义使
string str = string("Hello") + name + "."
这样的语句调用operator+(const string&&, const string& / const char*)
从而减少了拷贝次数; - 实现五个特殊成员函数时应尽量带上
noexcept
; - 右值引用类型的变量本身是左值;
- 返回值优化 NRVO:在一些简单条件下会直接在栈上构造对象,从而减少拷贝次数;
- xvalue
是一个可以被移动的值,并且与某个存储位置关联(如通过
std::move
生成的已命名对象);prvalue 是一个纯粹的值,不与任何存储位置关联(如一个整数字面值或一个计算的结果);glvalue (generalized lvalue) 包括 lvalue 和 xvalue;rvalue: 包括 xvalue 和 prvalue。 decltype<auto>
推导规则:- prvalue(例如临时变量)推导出 type;
- lvalue(例如有名字的对象)推导出 type&;
- xvalue(例如用 std::move()标记的对象)推导出 type&&;
异常
- 声明了
noexcept
的函数抛出异常会直接导致std::terminate
; - 特殊成员函数若内部调用的函数均为
noexcept
,则本身也为noexcept
; - 异常安全的四个等级:
- 无异常安全:抛出异常会进入未定义状态;
- 基本异常安全:抛出异常保证处于一致状态,但不保证保持原始状态(有副作用);
- 强异常安全:抛出异常将回滚;
- 不抛异常安全:不抛异常;
其它
- 一个迭代器必然支持
++
和*
,若支持*
输出则为输出迭代器;若支持*
读取则为输入迭代器,再可反复读取则为前向迭代器,再支持--
则为双向迭代器,再支持跳跃和比较则为随机访问迭代器,若还保证对象在内存连续存放则为连续迭代器(C++20); - C++11 会尽量使用移动返回对象,C++17 允许返回不可拷贝、不可移动的对象:直接构造于目标位置;新标准之后,应尽量使用返回值而非参数返回对象;
- C++11 引入了
char16_t
和char32_t
,分别代表 UTF-16 和 UTF-32;C++20 引入了用于存储 UTF-8 的char8_t
;
自动类型推断、字面量、静态断言
- C++17 之后
array
可以不带<int, 3>
而使用自动推断了;也允许使用auto [lower, upper] = Map.equal_range("four");
- C++11 允许使用大括号初始化列表在成员变量定义时默认初始化;
- C++11 允许使用
operator ""
定义下划线开头的字面量; - C++14 支持
0b
表示二进制字面量,支持在数字中添加'
使其更可读; - C++11 提供静态断言
static_assert(condition, output_info)
,可以直接在类定义中使用;
编译期多态、泛型编程与模板
- 模板元编程提供图灵完备的类型推导;
- 使用静态成员变量储存 value,
typedef
储存 type; - 使用替换失败非错(SFINAE)和
enable_if
实现条件编译; declval<T>
为类型 T 创建一个假设的右值引用,而不实际构造该类型的对象;它只有声明因此只能用于编译时上下文;
1 | // integral_constant |
函数式编程、可变模板和
**tuple**
的编译期技巧
[this]
按引用捕获外围对象,[*this]
按值捕获,调用拷贝(C++17);- lambda 表达式可以取代
bind
; - Map 在 C++ 中对应
transform
,Reduce 在 C++ 中对应accumulate
(C++17 已有reduce()
,要求归并操作的交换律和结合律,Filter 在 C++ 中对应copy_if
和partition
; - 实现 Compose:
1 | template <typename F, typename... Args> |
- 简化版
std::integer_sequence
和index_sequence
(C++14):
1 | template <class T, T... Ints> |
C++14 新特性
- 放宽
constexpr
限制; - 支持变量模板(
constexpr T pi = T(3.14)
)、别名模板(泛型关联:给模板定义类型起别名)与变参模板(参数个数可变); - 支持
auto
参数的泛型 lambda、lambda 初始化捕获; - 支持返回类型推导、
decltype<auto>
; - 聚合初始化(用初始化列表初始化结构体)、
make_unique()
; - 二进制字面量
0b110
、整型字面量分隔符12'345
; - 透明比较扩展:支持
std::set
和std::map
两个比较数不是同一类型,从而可以用const char*
查找std::set<std::string>
; std::shared_timed_mutex
与std::shared_lock
等;[[deprecated]]
、std::get
获取元组元素、 使用移动语义的std::exchange
;
C++17 新特性
- 结构化绑定
- 可以使用
auto [u, v] = myStruct
、auto [u, v] {myStruct}
或auto [u, v] (myStruct)
解构对象;也可以用在for (const auto& [key, val] : myMap)
中; - 本质上是匿名本地拷贝了
myStruct
(假设为e
),u
和v
分别是e
的成员,若使用auto&
,则e
为原对象引用,即修饰符并非作用于u
和v
; - 结构化绑定同样也适用于原生数组、
std::array
、std::pair
和std::tuple
不会产生类型退化;
- 可以使用
- 属性
[[nodiscard]]
:鼓励编译期在返回值未被使用时给警告;[[maybe_unused]]
:避免编译期在返回值未被使用时给警告;[[fallthrough]]
:避免编译期在switch
语句某个标签缺少break
时给警告;[[deprecated]]
:弃用;
- 带初始化的
if
和switch
语句:类似for
,可以使用auto
,定义变量仅在此语句中有效; - 在头文件中使用
inline static
定义 全局变量(必须直接初始化,且必须是字面类型);现在static constexpr
修饰符隐含inline
; - 聚合体可以拥有基类;C++17 中数组和满足一定条件的 类类型(
class
、struct
、union
)均被认为是 聚合体; - 强制省略拷贝或传递未实质化(从 prvalue 转化为
xvalue)的对象:比具名返回值优化(NRVO)更强,可以在
delete
禁止拷贝和移动函数的情况下工作; - 进一步放宽 lambda 和
constexpr
范围,支持constexpr
lambda; - 嵌套命名空间;
- 解决了部分表达式求值顺序无保证的问题;
- C++17 之后直接初始化
auto a{42}
会得到int
,拷贝初始化auto a = {42}
会得到initializer_list
; u8'6'
表示 UTF-8 字符字面量;- 异常声明将作为函数类型的一部分;
- 自动类模板参数推导;支持
std::pair p(a, b)
而不需make_pair
; - 使用编译期
if
语句if constexpr(...)
在编译期决定生成then
还是else
的代码; - 折叠表达式:有一些处理空参数包的规则;
1 | return (... + args); |
- 对模板能力的进一步扩展,可以使用
auto
和decltype<auto>
作模板参数; - 类型擦出容器和代数数据类型:
std::optional<T>
在空时为std::nullopt
;std::variant<int, double, std::string>
,更安全的union
;std::any
不需提前声明存储哪些类型,在取值时才通过模板参数指定,更安全的void*
;std::any
、std::optionalstd::variant
均为值语义,赋值时执行深拷贝;
- 字符串视图
std::string_view
:一段字符串的引用,不具有所有权,具备std::string
所有只读接口; std::apply(func, std::tuple(...))
将容器值作为函数输入;- C++17 采纳了 Boost.Filesystem 提供文件系统库,在
std::filesystem
下; - 支持类型特征后缀:可用
_v
代替::value
; - 支持使用
std::execution::par
调用并行 STL 能力,需要 tbb 库; - 新增泛型辅助函数
size()
、empty
、data()
; clamp()
找到三个值中居中的那个;sample()
获取一个随机子集;emplace()
系函数现在返回新插入对象的引用;try_emplace({key, std::move(value)})
在插入成功时才会移动;insert_or_assign()
无则插入有则替换,必然移动;std::vector
、std::list
和std::forward_list
开始支持不完全类型(可以在一个类中声明本身类型的vector
作为成员变量);- 提供
std::scoped_lock<>
同时锁住多个互斥量,std::shared_mutex<>
支持读写锁,是 C++14 的std::shared_timed_mutex<>
的子集; m.extract()
提取一个容器(可以用迭代器或键值指定),返回一个std::map<Key, T>::node_type
,此时m
不再包含此值;<numeric>
中新增std::gcd()
和std::lcm()
;- 大量新增算法 API、专家特性与辅助特性;
- 数字与字符串互转:C++11 有
std::to_string()
和std::stoi()
,C++17 新增std::from_chars()
和std::to_chars()
; - C++14 兼容 C99,而 C++17 兼容 C11;
- C++11 废除空异常声明
throw()
,C++17 废除动态异常声明throw(std::bad_alloc)
; register
C++17 起不再有语义,但关键字仍然保留;- 禁止
bool
使用++
,移除auto_ptr
;移除random_shuffle()
,使用std::shuffle()
代替; - 移除
std::unary_function
和std::binary_function
;
C++20 新特性
- 格式化输出:
1 | format("Hello {1} {0}", "second", "first"); // Hello first second |
进一步放宽
constexpr
范围;<utility>
使用cmp_less()
等比较两个不同类型的数;“宇宙飞船”
<=>
:- 返回一个
<compare>
中的对象,可以与 0 比较;若小于则返回值小于 0,大于则大于 0,等于则等于 0; - 若操作数为整型,则返回
strong_ordering::equal/less/greater
;若为浮点型,返回partial_ordering::equivalent/less/greater/unordered
; <=>
重载- C++20 后会将
a > b
重写为b <=> a < 0
,因此可以直接通过重载auto operator<=>(const Num&) const = default;
省去列举每个比较操作符的重载(默认按定义顺序依次将每个变量作为关键字),而只需再重载==
;
- 返回一个
协程(Coroutines):
- 无栈协程,使用
co_return
、co_await
、co_yield
;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21// sample 1
generator<int> range(int start, int end) {
while (start < end) {
co_yield start;
start++;
}
// co_return;
}
for (int n : range(0, 10)) {...}
// sample 2
task<void> echo(socket s) {
while (1) {
auto data = co_await s.async_read();
co_await async_write(s, data);
}
// co_return;
}- 无栈协程,使用
概念(Concepts):
requires Numeric<T>
约束(任意)模板,其中concept Numeric = integral<T> || floating_point<T>;
;运算数结果为bool
,结果需要为true
;- 可以直接写
template <Numeric T>
,或写入参数auto func(Numeric auto &arg);
; - 可以在函数、
lambda
中使用;三种约束的逻辑操作符:合取式(conjunctions)、析取式(disjunctions)、原子约束(atomic constraints),使用短路; - 以下代码可以一定程度代替虚函数,提供更好的 ABI 稳定性:
1
2template
concept DerivedOfBaseClass = std::is_base_of_v<BaseClass, T>;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16template<typename T>
concept Histogram = requires(T h1, T h2) {
h1.getMoments(); // 要求有getMoments接口
T::count; // 要求有静态变量count
h1.moments; // 要求有成员变量moments
h1 + h2; // 要求对象能够进行+操作
typename T::type; // 要求存在类型成员type
typename std::vector<T>; // 要求能够模板实例化并与std::vector组合使用
{ h1.getSubHistogram() } -> same_as<T>; // 要求接口返回类型与T一致
{ h1.getUnit() } -> convertible_to<float>; // 要求接口返回类型能转换成float,本质上接口返回类型可能是double
{ h1 = std::move(h2) } noexcept; // 要求表达式不能抛出异常
requires sizeof(T) > 4;
};模块(Modules):
- 一个编译单元默认为普通单元,使用
module
则为模块单元;使用export module my_module
的称为模块接口单元(对应传统头文件),使用module my_module
称为模块实现单元(对应传统实现文件);使用import my_module
导入模块; - 需要在函数签名、命名空间等前使用
export
对外公开;使用export import one_module
重导出模块(类似 Rust 中的pub use
); - 若模块单元需要
#include<>
,或其它全局代码段,则需要在开头写一句module;
引入全局模块,然后写预处理指令,写完后立即声明一个标准模块export module my_module
; - 实现部分和接口部分可以放在同一个文件中,接口部分前使用
export module my_module
,实现部分前使用module: private
; - 模块分区:
module A:B;
,导入时只需要指定分区名称:export import :A;
;模块分区的所有声明均对模块本身公开,export
使其可以对模块外公开,但最终还需要主模块接口单元决定,即模块分区单元中的符号必须通过主模块的接口单元使用export
控制对外可见性; - 可以使用
A.B
式模块名,但与A
之间并没有父子模块关系; - Module 并没有解决符号冲突问题,仍然需要与 namespace 配合使用,二者保持正交设计;同样不能解决二进制分发与 ABI 对齐问题;
- 一个编译单元默认为普通单元,使用
范围(Ranges):
- Range 是一个 Concept,要求是可迭代对象的集合,且支持
begin()
和end()
迭代器; - 视图是惰性迭代操作,从底层返回而不拥有任何数据,复杂度为 O(1);
- 使用
ranges::take_view(ranges, n)
返回前 n 个元素的视图,支持只读操作,同时支持for (int i: tv)
; - 视图适配器对函数式编程的支持:视图管道
auto ****result = nums | views::take(5) | views::reverse;
获取nums
前五个元素的翻转; nums | views::filter(lambda)
,nums | views::transform(lambda)
;views::reverse()
,views::iota(1, 10)
返回一系列递增值;stack
和queue
没有begin()
和end()
;ranges::sort(vec)
,views::drop(view)
;
- Range 是一个 Concept,要求是可迭代对象的集合,且支持
std::span
:C 数组的string_view
;直接用std::span<T>
传参将 C 数组升级为span
,从而传递其长度,且支持begin()
、front()
、empty()
等操作;注意它仍不检查越界;
1 | auto remove_it = std::remove(c.begin(), c.end(), v); // 返回后面向前移动后的末尾下一个 |
- 使用
[[likely]]
和[[unlikely]]
标记某个分支走到的概率; - 不建议隐式捕获
[=]
捕获this
,已弃用;使用[=, this]
显式捕获; lambda
中可以使用模板语法,可以捕获参数包;[&...args = std::forward<Args>(args)] {...}
;<bit>
头提供位操作;<numbers>
头中定义一些数学常量;std::make_shared<int[]>(5)
或std::make_shared<int[5]>()
支持数组;str.starts_with("foo")
和ends_with()
;map
和set
新增contains(key)
;std::midpoint(1, 3) == 2
不溢出地计算两个值的中点;std::to_array
将 array-like 对象转换为std::array
;- 新增一系列新库函数;
jthread
;编译时源代码分析source_location
库;u8string
;
C++23 新特性
- 显式
this
参数:this Self& self
,搭配模板实现同时定义函数的 const 和非 const 版本,也可以实现递归 lambda 函数; - 多元
operator[]
:支持逗号分隔的多维下标,可以搭配参数包转发;同时提供多维数组视图mdspan
,可以用于包装一维数组模拟多维数组; - 标准模块
std
与std.compact
; unexpected()
、and_then
、or_else
等借鉴自 Rust 等语言的函数,错误码类型类似 optional;- 进一步扩展 Ranges:转换函数
to
; print()
和println()
:配合format
输出;- 堆栈跟踪库
stacktrace
; - C++26 新特性(猜测):
- 静态反射;
- Executors:获取线程池对象,并分配任务;
- Sender / Receiver:Sender 创建后不立即执行调度任务,传递给 Receiver 后在执行;支持使用通用异步算法实现链式调用;Scheduler 返回 Sender 工厂;
- 标准网络库 Network;
- 高性能计算(线性代数等);
- Coroutines 扩展:
std::lazy
; - 进一步扩展 Ranges;
- Hive:Bucket Array 容器框架,用于高性能交易、游戏编程等场景的大量数据块操作;
- 多线程无锁内存模型;
- CPO 定制点对象:若引入的命名空间中仅有一个此函数名则无需写命名空间前缀;