CppGuide社区 CppGuide社区
首页
  • 🔥最新谷歌C++风格指南(含C++17/20)
  • 🔥C++17详解
  • 🔥C++20完全指南
  • 🔥C++23快速入门
🔥C++面试
  • 第1章 C++ 惯用法与Modern C++篇
  • 第2章 C++开发工具与调试进阶
  • 第3章 C++多线程编程从入门到进阶
  • 第4章 C++网络编程重难点解析
  • 第5章 网络通信故障排查常用命令
  • 第6章 网络通信协议设计
  • 第7章 高性能服务结构设计
  • 第8章 Redis网络通信模块源码分析
  • 第9章 服务其他模块设计
  • 🚀 全部章节.pdf 下载 (opens new window)
  • 🔥C++游戏编程入门(零基础学C++)
  • 🔥使用C++17从零开发一个调试器 (opens new window)
  • 🔥使用C++20从零构建一个完整的低延迟交易系统 (opens new window)
  • 🔥使用C++从零写一个C语言编译器 (opens new window)
  • 🔥从零用C语言写一个Redis
  • leveldb源码分析
  • libevent源码分析
  • Memcached源码分析
  • TeamTalk源码分析
  • 优质源码分享 (opens new window)
  • 🔥远程控制软件gh0st源码分析
  • 🔥Windows 10系统编程
  • 🔥Linux 5.x内核开发与调试 完全指南 (opens new window)
  • TCP源码实现超详细注释版.pdf (opens new window)
  • 高效Go并发编程
  • Go性能调优
  • Go项目架构设计
  • 🔥使用Go从零开发一个数据库
  • 🔥使用Go从零开发一个编译器 (opens new window)
  • 🔥使用Go从零开发一个解释器 (opens new window)
Rust编程指南
  • SQL零基础指南
  • MySQL开发与调试指南
GitHub (opens new window)
首页
  • 🔥最新谷歌C++风格指南(含C++17/20)
  • 🔥C++17详解
  • 🔥C++20完全指南
  • 🔥C++23快速入门
🔥C++面试
  • 第1章 C++ 惯用法与Modern C++篇
  • 第2章 C++开发工具与调试进阶
  • 第3章 C++多线程编程从入门到进阶
  • 第4章 C++网络编程重难点解析
  • 第5章 网络通信故障排查常用命令
  • 第6章 网络通信协议设计
  • 第7章 高性能服务结构设计
  • 第8章 Redis网络通信模块源码分析
  • 第9章 服务其他模块设计
  • 🚀 全部章节.pdf 下载 (opens new window)
  • 🔥C++游戏编程入门(零基础学C++)
  • 🔥使用C++17从零开发一个调试器 (opens new window)
  • 🔥使用C++20从零构建一个完整的低延迟交易系统 (opens new window)
  • 🔥使用C++从零写一个C语言编译器 (opens new window)
  • 🔥从零用C语言写一个Redis
  • leveldb源码分析
  • libevent源码分析
  • Memcached源码分析
  • TeamTalk源码分析
  • 优质源码分享 (opens new window)
  • 🔥远程控制软件gh0st源码分析
  • 🔥Windows 10系统编程
  • 🔥Linux 5.x内核开发与调试 完全指南 (opens new window)
  • TCP源码实现超详细注释版.pdf (opens new window)
  • 高效Go并发编程
  • Go性能调优
  • Go项目架构设计
  • 🔥使用Go从零开发一个数据库
  • 🔥使用Go从零开发一个编译器 (opens new window)
  • 🔥使用Go从零开发一个解释器 (opens new window)
Rust编程指南
  • SQL零基础指南
  • MySQL开发与调试指南
GitHub (opens new window)
  • C++20 完全指南 说明
  • 第1章 比较和<=>运算符
  • 第2章 函数参数的占位符类型
  • 第3章 概念、要求和约束
  • 第4章 概念、需求和约束详解
  • 第5章 标准概念详解
  • 第6章 范围与视图
  • 第7章 范围和视图的实用工具
  • 第8章 视图类型详解
  • 第9章 跨度(Spans)
  • 第10章 格式化输出
  • 第11章 <chrono>中的日期和时区
  • 第12章 std::jthread和停止令牌
  • 第13章 并发特性
  • 第14章 协程
  • 第15章 协程详解
  • 第16章 模块
  • 第17章 Lambda扩展
  • 第18章 编译期计算
  • 第19章 非类型模板参数(NTTP)扩展
  • 第20章 新的类型特性
  • 第21章 核心语言的小改进
  • 第22章 泛型编程的小改进
  • 第23章 C++标准库的小改进
    • 23.1 字符串类型的更新
      • 23.1.1 字符串成员函数starts_with()和ends_with()
      • 23.1.2 受限的字符串成员函数reserve()
    • 23.2 std::source_location
    • 23.3 整数数值和大小的安全比较
      • 23.3.1 整数数值的安全比较
      • 23.3.2 ssize()
    • 23.4 数学常量
    • 23.5 位处理工具
      • 23.5.1 位操作
      • 23.5.2 std::bit_cast<>
      • 23.5.3 std::endian
    • 23.6 <version>
    • 23.7 算法的扩展
      • 23.7.1 范围支持
      • 23.7.2 新算法
      • 针对范围的min()、max()和minmax()
      • shift_left()和shift_right()
      • lexicographical_compare_three_way()
      • 23.7.3 算法的unseq执行策略
    • 23.8 补充说明
  • 第24章 已弃用和移除的特性
  • cpp20completeguides
zhangxf
2025-03-20
目录

第23章 C++标准库的小改进

# 第23章 C++标准库的小改进

本章介绍C++20标准库中尚未在本书中提及的其他特性和扩展。

# 23.1 字符串类型的更新

在C++20中,字符串类型的某些方面发生了变化。这些变化影响到字符串(std::basic_string<>及其实例化类型,如std::string)、字符串视图(std::basic_string_view<>及其实例化类型,如std::string_view),或者两者都有影响。

事实上,C++20为字符串类型带来了以下改进:

  • 所有字符串类型现在都支持三向比较运算符<=>。为此,它们现在只声明operator==和operator<=>,不再声明operator!=、operator<、operator<=、operator>和operator>=。
  • 所有字符串类型现在都提供了新的成员函数starts_with()和ends_with()。
  • 对于字符串,成员函数reserve()不能再用于请求缩小字符串的容量(为其值分配的内存)。因此,不能再向reserve()传递无参数调用。
  • 对于UTF - 8字符,C++现在提供了字符串类型std::u8string和std::u8string_view。它们被定义为针对新的UTF - 8字符类型char8_t的std::basic_string<>和std::basic_string_view<>。因此,返回UTF - 8字符串的库函数现在的返回类型为std::u8string。请注意,在切换到C++20时,这个变化可能会破坏现有代码。
  • 字符串(std::string和std::basic_string<>的其他实例化类型)现在是constexpr的,这意味着可以在编译时使用字符串。

注意,不能在编译时和运行时同时使用std::string。不过,有办法将编译时字符串导出到运行时。

  • 字符串视图现在被标记为视图和借用范围。
  • 为std::u8string和std::u8string_view类型,以及std::pmr::string、std::pmr::u8string、std::pmr::u16string、std::pmr::u32string和std::pmr::wstring添加了标准哈希函数。

以下各节将解释其他章节未介绍和说明的重要改进。

# 23.1.1 字符串成员函数starts_with()和ends_with()

现在,字符串和字符串视图都有了新的成员函数starts_with()和ends_with()。它们提供了一种简便的方法,用于检查字符串的开头和结尾字符是否与特定的字符序列匹配。你可以将其与单个字符、字符数组,或者字符串或字符串视图进行比较。

例如:

void  foo(const  std::string&  s,  std::string_view  suffix) {
    if  (s.starts_with('.'))  {
       ...
    }
    if  (s.ends_with(".tmp"))  {
       ...
    }
    if  (s.ends_with(suffix))  {
       ...
    }
}
1
2
3
4
5
6
7
8
9
10
11

# 23.1.2 受限的字符串成员函数reserve()

对于字符串,成员函数reserve()不能再用于请求缩小字符串的容量(为其值分配的内存):

void modifyString(std::string&  s) {
    if  (...)  {
        s.clear();
        s.reserve(0);   // 可能不再像某些平台之前那样缩小内存
        return;
    }
   ...
}
1
2
3
4
5
6
7
8

这是因为释放内存可能需要一些时间,这意味着在移植代码时,这个调用的性能可能会有显著差异。

因此,不再支持向reserve()传递无参数调用:

s.reserve();  		// 自C++20起错误
1

应使用shrink_to_fit()代替:

s.shrink_to_fit();  // 仍然可行
1

# 23.2 std::source_location

有时,让程序处理当前正在处理的源代码位置非常重要。这在日志记录、测试和检查不变量时特别有用。到目前为止,程序员不得不使用C预处理器宏__FILE__、__LINE__和__func__。C++20为此引入了一种类型安全的特性,使得一个对象可以用当前源代码位置进行初始化,并且这个信息可以像任何其他对象一样被传递。

使用方法很简单:

#include  <source_location>

void  foo() {
    auto  sl  =  std::source_location::current();
   ...
    std::cout  <<  "file :      "  <<  sl.file_name()  <<  "\n";
    std::cout  <<  "function :  "  <<  sl.function_name()  <<  "\n";
    std::cout  <<  "line/col:   "  <<  sl.line()  <<  "/"  <<  sl.column()  <<  "\n";
}
1
2
3
4
5
6
7
8
9

静态consteval函数std::source_location::current()会生成一个std::source_location类型的对象,用于表示当前源代码位置,该对象具有以下接口:

  • file_name()返回文件名。
  • function_name()返回函数名(如果在任何函数外部调用,则为空)。
  • line()返回行号(如果不知道行号,可能为0)。
  • column()返回该行中的列号(如果不知道列号,可能为0)。

函数名的具体格式和列号的具体位置等细节可能会有所不同。例如,使用GCC库时,输出可能如下:

file:     sourceloc.cpp
function: void  foo()
line/col: 8/42
1
2
3

而Visual C++的输出可能如下:

file:     sourceloc.cpp
function: foo
line/col: 8/35
1
2
3

注意,通过在参数声明中使用std::source_location::current()作为默认参数,可以获取函数调用的位置。例如:

void  bar(std::source_location  sl  =  std::source_location::current()) {
   ...
    std::cout  <<  "file :      "  <<  sl.file_name()  <<  "\n";
    std::cout  <<  "function :  "  <<  sl.function_name()  <<  "\n";
    std::cout  <<  "line/col:   "  <<  sl.line()  <<  "/"  <<  sl.column()  <<  "\n";
}

int main() {
   ...
    bar();
   ...
}
1
2
3
4
5
6
7
8
9
10
11
12

输出可能如下:

file:     sourceloc.cpp
function: int main()
line/col: 34/6
1
2
3

或者:

file:     sourceloc.cpp
function: main
line/col: 34/3
1
2
3

因为std::source_location是一个对象,所以可以将其存储在容器中并进行传递:

std::source_location myfunc() {
    auto  sl  =  std::source_location::current();
   ...
    return  sl;
}

int main() {
    std::vector<std::source_location>  locs;
   ...
    locs.push_back(myfunc());
   ...
    for  (const  auto&  loc  :  locs)  {
        std::cout  <<  "called :  "  <<  loc.function_name()  <<  "\n";
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

输出可能如下:

called: ’std::source_location myfunc()’
1

或者:

called: ’myfunc’
1

完整示例见lib/sourceloc.cpp。

# 23.3 整数数值和大小的安全比较

比较不同类型的整数值比预期的要复杂。本节介绍C++20中用于处理这个问题的两个新特性:

  • 整数比较工具
  • std::ssize()

# 23.3.1 整数数值的安全比较

比较和转换数字,即使是不同数值类型的数字,本应是一项简单的任务。但不幸的是,事实并非如此。几乎所有程序员都见过关于此类代码的警告。这些警告是有充分理由的:由于隐式转换,我们可能在不知不觉中编写了不安全的代码。

例如,大多数时候我们期望简单的x < y能正常工作。考虑以下示例:

int  x  =  -7;
unsigned  y  =  42;
if  (x  <  y)  ...   // 糟糕:结果为false
1
2
3

出现这种情况的原因是,根据(编程语言C的)规则,在比较有符号值和无符号值时,有符号值会被转换为无符号类型,这使得它变成一个很大的正整数值。

要修复这段代码,你必须改为如下写法:

if  (x  <  static_cast<int>(y))  ...    // 结果为true
1

如果我们比较小的整数值和大的整数值,还可能会出现其他问题。

C++20现在在<utility>头文件中提供了用于安全整数比较的函数。表“安全整数比较函数”列出了这些函数。你可以使用它们,简单地写成:

if  (std::cmp_less(x,  y))  ...         // 结果为true
1

这种整数比较始终是安全的。

常量 模板
std::cmp_equal(x , y )
std::cmp_not_equal(x , y )
std::cmp_less(x , y )
std::cmp_less_equal(x , y )
std::cmp_greater(x , y )
std::cmp_greater_equal(x , y )
std::in_range<T>(x)
返回x是否等于y
返回x是否不等于y
返回x是否小于y
返回x是否小于或等于y
返回x是否大于y
返回x是否大于或等于y
返回x对于类型T是否是一个有效值

表23.1 安全整数比较函数

函数std::in_range()用于判断传入的值是否是能由传入类型表示的值,如果是则返回true:

bool  b  =  std::in_range<int>(x);   // 如果x是有效的int值,则返回true
1

它只是判断x是否大于或等于传入类型的最小值,并且小于或等于最大值。

请注意,这些函数不能用于比较bool、字符类型或std::byte的值。

# 23.3.2 ssize()

我们经常需要将集合、数组或范围的大小作为有符号值。例如,为了避免这里的警告:

for  (int  i  =  0;  i  <  coll.size();  ++coll)  { // 可能会出现警告
   ...
}
1
2
3

问题在于size()返回的是无符号值,当值非常大时,有符号值和无符号值之间的比较可能会出错。

虽然你可以将i声明为unsigned int或std::size_t,但有时将其声明为int可能有充分的理由。

引入的辅助函数std::ssize()允许这样使用:

for  (int  i  =  0;  i  <  std::ssize(coll);  ++coll)  {   // 通常不会出现警告
   ...
}
1
2
3

多亏了参数依赖查找(ADL,Argument-Dependent Lookup),当使用标准容器或其他标准类型时,写成如下形式就足够了:

for  (int  i  =  0;  i  <  ssize(coll);  ++coll)  {        // 对于标准类型没问题
   ...
}
1
2
3

请注意,有时为了避免编译时错误,这甚至是必要的。例如在初始化闩锁(latch)、屏障(barrier)和信号量(semaphore)时。

还要注意,范围库(ranges library)在命名空间std::ranges中也提供了ssize()。

# 23.4 数学常量

C++20引入了最重要的数学浮点常量。表“数学常量”列出了这些常量。

这些常量在<numbers>头文件的std::numbers命名空间中提供。

常量 模板
std::numbers::e
std::numbers::pi
std::numbers::inv_pi
std::numbers::inv_sqrtpi
std::numbers::sqrt2
std::numbers::sqrt3
std::numbers::inv_sqrt3
std::numbers::log2e
std::numbers::log10e
std::numbers::ln2
std::numbers::ln10
std::numbers::egamma
std::numbers::phi
std::numbers::e_v<>
std::numbers::pi_v<>
std::numbers::inv_pi_v<>
std::numbers::inv_sqrtpi_v<>
std::numbers::sqrt2_v<>
std::numbers::sqrt3_v<>
std::numbers::inv_sqrt3_v<>
std::numbers::log2e_v<>
std::numbers::log10e_v<>
std::numbers::ln2_v<>
std::numbers::ln10_v<>
std::numbers::egamma_v<>
std::numbers::phi_v<>

表23.2 数学常量

这些常量是对应变量模板的double类型特化,变量模板带有后缀_v。这些值是相应类型最接近的可表示值。例如:

namespace  std::number  {
    template<std::floating_point  T>  
    inline  constexpr  T  pi_v<T>  =  ... ;  
    inline  constexpr  double  pi  =  pi_v<double>;
}
1
2
3
4
5

如你所见,这些定义使用了std::floating_point概念(引入该概念就是出于这个目的)。

因此,你可以如下使用它们:

#include  <numbers>
...
double  area1  =  rad  *  rad  *  std::numbers::pi;
long  double  area2  =  rad  *  rad  *  std::numbers::pi_v<long  double>;
1
2
3
4

# 23.5 位处理工具

C++20为位处理提供了更好、更简洁的支持:

  • 补充缺失的低级位操作。
  • 位转换。
  • 检查平台的字节序。

所有这些工具都在<bit>头文件中定义。

# 23.5.1 位操作

硬件通常对“左旋转”或“右旋转”等位操作有特殊支持。然而,在C++20之前,C++程序员无法直接访问这些指令。新引入的位操作提供了直接访问底层CPU位指令的API。

表“位操作”列出了C++20引入的所有标准化位操作。它们在<bit>头文件中作为独立函数在std命名空间中提供。

操作 含义
rotl(val, n)
rotr(val, n)
countl_zero(val)
countl_one(val)
countr_zero(val)
countr_one(val)
popcount(val)
has_single_bit(val)
bit_floor(val)
bit_ceil(val)
bit_width(val)
返回将val的二进制位向左旋转n位后的结果
返回将val的二进制位向右旋转n位后的结果
返回前导(最高有效位)0位的数量
返回前导(最高有效位)1位的数量
返回尾随(最低有效位)0位的数量
返回尾随(最低有效位)1位的数量
返回值中1位的数量
返回val是否为2的幂(只有一位被设置)
返回小于或等于val的最大的2的幂值
返回大于或等于val的最小的2的幂值
返回存储该值所需的位数

表23.3 位操作

考虑以下程序:

// lib/bitops8.cpp
#include  <iostream>
#include  <format>
#include  <bitset>
#include  <bit>

int main() {
    std::uint8_t  i8  =  0b0000’1101;
    std::cout
        <<  std::format( "{0:08b}  {0:3}\n " ,  i8)
        <<  std::format( "{0:08b}  {0:3}\n " ,  std::rotl(i8,  2))   
        <<  std::format( "{0:08b}  {0:3}\n " ,  std::rotr(i8,  1))   
        <<  std::format( "{0:08b}  {0:3}\n " ,  std::rotr(i8,  -1))
        <<  std::format( "{}\n " ,  std::countl_zero(i8))
        <<  std::format( "{}\n " ,  std::countr_one(i8))   
        <<  std::format( "{}\n " ,  std::popcount(i8))
        <<  std::format( "{}\n " ,  std::has_single_bit(i8))
        <<  std::format( "{0:08b}  {0:3}\n " ,  std::bit_floor(i8))
        <<  std::format( "{0:08b}  {0:3}\n " ,  std::bit_ceil(i8))
        <<  std::format( "{}\n " ,  std::bit_width(i8));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

该程序的输出如下:

00001101    13
00110100    52
10000110  134
00011010    26
4
1
3
false
00001000      8
00010000    16
4
1
2
3
4
5
6
7
8
9
10
11

请注意以下几点:

  • 只有当传入的类型是无符号整数类型时,才会提供所有这些函数。
  • 旋转函数也接受负的n,这意味着旋转方向会改变。
  • 所有返回计数的函数返回类型都是int。唯一的例外是bit_width(),它返回传入类型的值,这在标准中是不一致的(我认为是个错误)。

因此,当将其作为int使用或直接打印时,可能需要使用static_cast。

如果你针对std::uint16_t运行相应的程序(见lib/bitops16.cpp),会得到以下输出:

0000000000001101        13
0000000000110100        52
1000000000000110  32774
0000000000011010        26
12
1
3
false
0000000000001000          8
0000000000010000        16
4
1
2
3
4
5
6
7
8
9
10
11

还要注意,这些函数仅为无符号整数类型定义。这意味着:

  • 不能对有符号整数类型使用位操作:
int  b1  =    ... ;
auto  b2  =  std::rotl(b1,  2);     // 错误
1
2
  • 不能对char类型使用位操作:
char  b1  =    ... ;
auto  b2  =  std::rotl(b1,  2);     // 错误
1
2

unsigned char类型则可以正常使用。

  • 不能对std::byte类型使用位操作:
std::byte  b1{ ... };
auto  b2  =  std::rotl(b1,  2);     // 错误
1
2

表“位操作的硬件支持”(取自http://wg21.link/p0553r4 (opens new window))列出了一些新位操作到现有硬件指令的可能映射。

操作 Intel/AMD ARM PowerPC
rotl() ROL – rldicl
rotr() ROR ROR, EXTR –
popcount() POPCNT – popcntb
countl_zero() BSR, LZCNT CLZ cntlzd
countl_one() – CLS –
countr_zero() BSF, TZCNT – –
countr_one() – – –

表23.4 位操作的硬件支持

# 23.5.2 std::bit_cast<>

C++20提供了一种新的类型转换操作std::bit_cast<>,用于改变位序列的类型。与使用reinterpret_cast<>或联合(unions)不同,std::bit_cast<>运算符确保位的数量匹配,使用标准布局,并且不使用指针类型。

例如:

std::uint8_t b8 = 0b0000’1101;

auto bc = std::bit_cast<char>(b8);     	// 正确
auto by = std::bit_cast<std::byte>(b8);	// 正确
auto bi = std::bit_cast<int>(b8);		// 错误:位数量错误
1
2
3
4
5

# 23.5.3 std::endian

C++20引入了一个新的实用枚举类型std::endian,可用于检查执行环境的字节序。它引入了三个枚举值:

  • std::endian::big,表示 “大端序”(scalar types),即标量类型存储时最高有效字节在前,其余字节按降序排列。
  • std::endian::little,表示 “小端序”,即标量类型存储时最低有效字节在前,其余字节按升序排列。
  • std::endian::native,指定执行环境的字节序。

如果所有标量类型都是大端序,std::endian::native等于std::endian::big。如果所有标量类型都是小端序,std::endian::native等于std::endian::little。否则,std::endian::native的值既不是std::endian::big也不是std::endian::little。

如果所有标量类型的大小都为1,那么std::endian::little、std::endian::big和std::endian::native的值都相同。

该类型在头文件<bit>中定义。

作为枚举值,这些值可以在编译时使用。例如:

#include <bit>
// ...
if constexpr (std::endian::native == std::endian::big) {
    // ...          // 处理大端序
}
else if constexpr (std::endian::native == std::endian::little) {
    // ...          // 处理小端序
}
else {
    // ...          // 处理混合字节序
}
1
2
3
4
5
6
7
8
9
10
11

# 23.6 <version>

C++20引入了一个新的头文件<version>。这个头文件不提供实际的功能,而是提供了所有与正在使用的C++标准库相关的特定于实现的常规信息。

例如,<version>可能包含:

  • C++标准库的版本和发布日期。
  • 版权信息。

此外,<version>定义了C++标准库的特性测试宏(它们也在相关的头文件中定义)。

由于这个头文件简短且加载速度快,工具可以包含这个头文件,以获取所有必要的信息,从而根据提供的特性集做出决策,或者找到处理所使用库的常规信息所需的所有内容。

# 23.7 算法的扩展

对于算法,C++20提供了一些扩展(其中一些在本书的其他章节已经描述过)。

# 23.7.1 范围支持

如在关于范围(ranges)的章节中所讨论的,现在许多算法支持:

  • 将整个范围(容器、视图)作为单个参数传递。
  • 将投影(projection)参数作为单个参数传递。

这种支持要求在std::ranges命名空间中调用算法。以下算法(目前)还不支持范围:

  • 数值算法(如accumulate())。
  • 具有并行执行的算法。
  • 算法lexicographical_compare_three_way()。

有关所有标准算法对范围支持的更多详细信息,请参阅算法概述。

# 23.7.2 新算法

下表列出了C++20引入的标准算法。

名称 效果
min()
max()
minmax()
返回传递范围的最小值
返回传递范围的最大值
返回传递范围的最小值和最大值
shift_left()
shift_right()
将所有元素向前移动
将所有元素向后移动
lexicographical_compare_three_way() 使用<=>运算符对两个范围进行排序

表23.5 新的标准算法

# 针对范围的min()、max()和minmax()

算法std::ranges::min()、std::ranges::max()和std::ranges::minmax()随范围库一起引入,用于返回传递范围的最小值和 / 或最大值。你可以选择传递比较准则和投影。

例如:

// lib/minmax.cpp
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector coll{0, 8, 15, 47, 11};
    std::cout << std::ranges::min(coll) << '\n';
    std::cout << std::ranges::max(coll) << '\n';
    auto [min, max] = std::ranges::minmax(coll);
    std::cout << min << ' ' << max << '\n';
}
1
2
3
4
5
6
7
8
9
10
11
12

该程序的输出如下:

0
47
0  47
1
2
3

注意,std命名空间中没有对应的接受两个迭代器的算法。只有接受两个值或一个std::initializer_list<>(以及一个比较准则)作为参数的函数。和往常一样,范围库也为它们提供了传递投影的选项。

# shift_left()和shift_right()

有了新的标准算法shift_left()和shift_right(),你可以分别将元素向前或向后移动。这些算法分别返回新的末尾或开头位置。

例如:

// lib/shift.cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include <ranges>

void print (const auto& coll) {
    for (const auto& elem : coll) {
        std::cout << elem << ' ';
    }
    std::cout << '\n';
}

int main() {
    std::vector coll{1, 2, 3, 4, 5, 6, 7, 8};
    print(coll);

    // 将一个元素向前移动(返回新的末尾位置):
    std::shift_left(coll.begin(), coll.end(), 1);
    print(coll);

    // 将三个元素向后移动(返回新的开头位置):
    auto newbeg = std::shift_right(coll.begin(), coll.end(), 3);
    print(coll);
    print(std::ranges::subrange{newbeg, coll.end()});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

该程序的输出如下:

1  2  3  4  5  6  7  8
2  3  4  5  6  7  8  8
2  3  4  2  3  4  5  6
2  3  4  5  6
1
2
3
4

你可以传递一个执行策略以允许使用多个线程。然而,(目前)不支持传递单参数范围和 / 或投影(这可能是个疏忽)。

# lexicographical_compare_three_way()

为了以返回新的比较类别类型之一的方式比较两个不同容器的元素,C++20引入了算法std::lexicographical_compare_three_way()。在关于<=>运算符的章节中对其进行了描述。

注意,对于这个算法,(目前)还不支持范围。你既不能将范围作为单个参数传递,也不能传递投影参数(这可能是个疏忽)。

# 23.7.3 算法的unseq执行策略

C++17为新引入的并行算法提供了多种执行策略。你可以启用并行计算,让线程并行处理多个数据项(称为向量化或单指令多数据(SIMD)处理)。

然而,此前无法让算法处理多个数据项的同时,限制算法仅使用一个线程。为此,C++20提供了执行策略std::execution::unseq。和往常一样,新的执行策略是std::execution命名空间中一个对应唯一类unsequenced_policy的constexpr对象。表23.6“执行策略”列出了目前支持的所有标准化执行策略。

策略 含义
std::execution::seq
std::execution::par
std::execution::unseq
std::execution::par_unseq
使用单个线程顺序执行单个值
使用多个线程并行执行单个值
使用单个线程并行执行多个值(自C++20起)
使用多个线程并行执行多个值

表23.6 执行策略

无序执行策略(unsequenced execution policy)允许在单个执行线程上交错执行访问元素的操作。你不应将此策略与阻塞同步(如使用互斥锁)一起使用,因为这可能会导致死锁。

以下是使用该策略的示例:

// lib/unseq.cpp
#include <iostream>
#include <vector>
#include <cmath>
#include <algorithm>
#include <execution>

int main  (int argc, char** argv) {
    // 从命令行初始化参数数量(默认值:1000):
    int numElems = 1000;
    if (argc > 1) {
        numElems = std::atoi(argv[1]);
    }

    // 初始化包含不同双精度值的向量:
    std::vector<double> coll;
    coll.reserve(numElems);
    for (int i=0; i<numElems; ++i) {
        coll.push_back(i * 4.37);
    }

    // 处理平方根:
    // - 允许SIMD处理,但仅使用一个线程
    std::for_each(std::execution::unseq,    // 自C++20起
                  coll.begin(), coll.end(), [](auto& val) {
                      val = std::sqrt(val);
                  });

    for (double value : coll) {
        std::cout << value << "\n";
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

和执行策略的通常情况一样,你无法影响该策略的使用时机和方式。使用此策略,你可以启用向量化或SIMD计算,但并不强制使用。在不支持此策略的硬件上,或者如果实现运行时决定不使用它(例如,因为CPU负载过高),unseq策略会导致顺序执行。

# 23.8 补充说明

字符串成员函数starts_with()和ends_with()由米哈伊尔·马尔采夫(Mikhail Maltsev)在http://wg21.link/p0457r2 (opens new window)中提议并被接受。

字符串reserve()函数的限制最初由安德鲁·罗(Andrew Luo)在http://wg21.link/lwg2968 (opens new window)中提出。最终被接受的措辞由马克·泽伦(Mark Zeren)和安德鲁·罗在http://wg21.link/p0966r1 (opens new window)中制定。

对源位置的访问最初由罗伯特·道格拉斯(Robert Douglas)在http://wg21.link/n3972 (opens new window)中提出。最终被接受的措辞由罗伯特·道格拉斯、科朗坦·贾博(Corentin Jabot)、丹尼尔·克尔(Daniel Kr)在http://wg21.link/p1208r6 (opens new window)中制定。

用于安全整数比较的函数由费德里科·基尔海斯(Federico Kircheis)在http://wg21.link/p0586r2 (opens new window)中提议并被接受。

ssize()函数作为解决有符号索引与无符号大小比较问题的方案被讨论了很长时间。我们希望所有size()函数都返回有符号值,然而这存在向后兼容性问题。作为对std::span使用有符号大小类型提案的回应,ssize()函数由罗伯特·道格拉斯、内文·利伯(Nevin Liber)和马歇尔·克洛(Marshall Clow)在http://wg21.link/p1089r0 (opens new window)中首次提出。最终被接受的措辞由约尔格·布朗(Jorg Brown)在http://wg21.link/p1227r2 (opens new window)中制定。std::ranges::ssize()后来由汉内斯·豪斯韦德尔(Hannes Hauswedell)、约尔格·布朗和凯西·卡特(Casey Carter)在http://wg21.link/p1970r2 (opens new window)中添加。

数学常量最初由列夫·明科夫斯基(Lev Minkovsky)在http://wg21.link/p0631r0 (opens new window)中提出。最终被接受的措辞由列夫·明科夫斯基和约翰·麦克法兰(John McFarlane)在http://wg21.link/p0631r8 (opens new window)中制定。

位操作最初由马修·菲奥拉万特(Matthew Fioravante)在http://wg21.link/n3864 (opens new window)中提出。最终被接受的措辞由延斯·毛雷尔(Jens Maurer)在http://wg21.link/p0553r4 (opens new window)和http://wg21.link/p0556r3 (opens new window)中制定。后来,文森特·勒韦尔迪(Vincent Reverdy)在http://wg21.link/p1956r1 (opens new window)中提议修改了这些操作的名称。

std::bit_cast<>由JF·巴斯蒂安(JF Bastien)在http://wg21.link/p0476r2 (opens new window)中提议并最终引入。

std::endian最初由霍华德·欣南特(Howard Hinnant)在http://wg21.link/p0463r0 (opens new window)中提出。最终被接受的措辞由霍华德·欣南特在http://wg21.link/p0463r1 (opens new window)中制定。后来,沃尔特·E·布朗(Walter E. Brown)和亚瑟·奥德怀尔(Arthur O’Dwyer)在http://wg21.link/p1612r1 (opens new window)中提议修正了相关头文件。

头文件<version>由艾伦·塔尔博特(Alan Talbot)在http://wg21.link/p0754r2 (opens new window)中提议并被接受。

新算法shift_left()和shift_right()由丹·拉维夫(Dan Raviv)在http://wg21.link/p0769r2 (opens new window)中提议并被接受。范围的新min/max算法随着范围库在http://wg21.link/p0896r4 (opens new window)中提议被引入。

unseq执行策略最初由阿奇·D·罗宾逊(Arch D. Robison)、巴勃罗·哈尔彭(Pablo Halpern)、罗伯特·格瓦(Robert Geva)和克拉克·尼尔森(Clark Nelson)在http://wg21.link/p0076r0 (opens new window)中提出。最终被接受的措辞由阿利斯代尔·梅雷迪思(Alisdair Meredith)和巴勃罗·哈尔彭在http://wg21.link/p1001r2 (opens new window)中制定。

上次更新: 2025/03/20, 19:44:38
第22章 泛型编程的小改进
第24章 已弃用和移除的特性

← 第22章 泛型编程的小改进 第24章 已弃用和移除的特性→

最近更新
01
C++语言面试问题集锦 目录与说明
03-27
02
第四章 Lambda函数
03-27
03
第二章 关键字static及其不同用法
03-27
更多文章>
Copyright © 2024-2025 沪ICP备2023015129号 张小方 版权所有
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式