C++ 参考手册
- C++11
- C++14
- C++17
- C++20
- C++ 编译器支持情况表
- 独立与宿主实现
- C++ 语言
- 变量模板(C++14 起)
- 整数字面量
- 聚合初始化
- 比较运算符
- 默认比较(C++20 起)
- 转义序列
- for 循环
- while 循环
- 用户定义转换
- SFINAE
- 主函数
- ASCII 码表
- 标识符
- 类型
- 内存模型
- 对象
- 基本概念
- 表达式
- 声明
- 初始化
- 函数
- 语句
- 类
- 运算符重载
- 模板
- 异常
- 事务性内存
- 占位符类型说明符 (C++11 起)
- decltype 说明符
- 函数声明
- final 说明符 (C++11 起)
- override 说明符(C++11 起)
- 引用声明
- 移动构造函数
- 移动赋值运算符
- 枚举声明
- constexpr 说明符(C++11 起)
- 列表初始化 (C++11 起)
- 构造函数与成员初始化器列表
- using 声明
- nullptr,指针字面量
- 基础类型
- 类型别名,别名模版 (C++11 起)
- 形参包
- 联合体声明
- 字符串字面量
- 用户定义字面量 (C++11 起)
- 属性说明符序列(C++11 起)
- Lambda 表达式 (C++11 起)
- noexcept 说明符 (C++11 起)
- noexcept 运算符 (C++11 起)
- alignof 运算符(C++11 起)
- alignas 说明符 (C++11 起)
- 存储类说明符
- 基于范围的 for 循环 (C++11 起)
- static_assert 声明
- 隐式转换
- 代用运算符表示
- 自增/自减运算符
- 折叠表达式(C++17 起)
- 类模板实参推导(C++17 起)
- 模板形参与模板实参
- if 语句
- inline 说明符
- 结构化绑定声明 (C++17 起)
- switch 语句
- 字符字面量
- 命名空间
- 求值顺序
- 复制消除
- consteval 说明符 (C++20 起)
- constinit 说明符 (C++20 起)
- 协程 (C++20)
- 模块 (C++20 起)
- 约束与概念 (C++20 起)
- new 表达式
- do-while 循环
- continue 语句
- break 语句
- goto 语句
- return 语句
- 动态异常说明
- throw 表达式
- try 块
- 命名空间别名
- 类声明
- cv(const 与 volatile)类型限定符
- 默认初始化
- 值初始化(C++03 起)
- 零初始化
- 复制初始化
- 直接初始化
- 常量初始化
- 引用初始化
- 值类别
- C++ 运算符优先级
- 布尔字面量
- 浮点字面量
- typedef 说明符
- 显式类型转换
- static_cast 转换
- dynamic_cast 转换
- const_cast 转换
- reinterpret_cast 转换
- delete 表达式
- 构造函数与成员初始化器列表
- this 指针
- 访问说明符
- 友元声明
- virtual 函数说明符
- explicit 说明符
- 静态成员
- 默认构造函数
- 复制构造函数
- 复制赋值运算符
- 析构函数
- 类模板
- 函数模板
- 显式(全)模板特化
- 汇编声明
- C++ 的历史
- 作用域
- 生存期
- 定义与单一定义规则(ODR)
- 名字查找
- 有限定的名字查找
- 无限定的名字查找
- 如同规则
- 未定义行为
- 翻译阶段
- 常量表达式
- 赋值运算符
- 算术运算符
- 逻辑运算符
- 成员访问运算符
- 其他运算符
- sizeof 运算符
- typeid 运算符
- 指针声明
- 数组声明
- 语言链接
- 详述类型说明符
- 默认实参
- 变长实参
- 实参依赖查找
- 重载决议
- 重载函数的地址
- 注入类名
- 非静态数据成员
- 非静态成员函数
- 嵌套类
- 派生类
- 空基类优化
- 抽象类
- 位域
- 转换构造函数
- 成员模板
- 模板实参推导
- 部分模板特化
- sizeof... 运算符
- 待决名
- 函数 try 块
- 扩充命名空间 std
- 字母缩写
- RAII
- 三/五/零之法则
- PImpl
- 零开销原则
- 类型
- 隐式转换
- 注释
- C++ 关键词
- 预处理器
- C++ 标准库头文件
- 具名要求
- 功能特性测试 (C++20)
- 工具库
- 类型支持(基本类型、RTTI、类型特性)
- 概念库 (C++20)
- 错误处理
- 动态内存管理
- 日期和时间工具
- 字符串库
- 容器库
- 迭代器库
- 范围库 (C++20)
- 算法库
- 数值库
- 输入/输出库
- 文件系统库
- 本地化库
- 正则表达式库
- 原子操作库
- 线程支持库
- 实验性 C++ 特性
- 有用的资源
- 索引
- std 符号索引
- 协程支持 (C++20)
- C++ 关键词
事务性内存
事务性内存(transactional memory)是在事务中结合语句组的并发同步机制,事务具有
- 原子性(atomic)(要么语句全部发生,要么全部不发生)
- 隔离性(isolated)(事务中的语句不会观察到另一事务写入一半,即使它们并行执行)
典型实现在受支持的平台上将硬件事务性内存使用到极致(例如,直至变更集饱和),再退回到软件事务性内存,后者常以乐观并发(optimistic concurrency)来实现:若另一事务更新了事务所用的某些变量,则该事务会安静地重试。由于这个原因,可重试事务(“原子块”)只能调用事务安全的函数。
注意,在事务内和事务外访问变量而无其他的外部同步,是数据竞争。
若支持功能特性测试,则本页所描述的功能特性,由具有大于或等于 201505 的值的宏常量 __cpp_transactional_memory 标明。
同步块
synchronized
复合语句
如同在一个全局锁下执行复合语句:程序中的所有最外层同步块都以一个单独的全序执行。在该顺序中,每个同步块的结尾同步于(synchronize with)下个同步块的开始。内嵌于其他同步块的同步块没有特殊语义。
同步块不是事务(不同于后面的原子块),并可以调用事务不安全的函数。
#include <iostream> #include <vector> #include <thread> int f() { static int i = 0; synchronized { // 开始同步块 std::cout << i << " -> "; ++i; // 每次调用 f() 都获得 i 的唯一值 std::cout << i << '\n'; return i; // 结束同步块 } } int main() { std::vector<std::thread> v(10); for(auto& t: v) t = std::thread([]{ for(int n = 0; n < 10; ++n) f(); }); for(auto& t: v) t.join(); }
输出:
0 -> 1 1 -> 2 2 -> 3 ... 99 -> 100
以任何方式(抵达结尾,执行 goto、break、continue 或 return,或抛出异常)离开同步块都会退出该块,而若所退出的块是外层块,则这在单一全序中同步于下个同步块。若使用 std::longjmp 退出同步块则行为未定义。
不允许用 goto 或 switch 进入同步块。
尽管同步块如同在一个全局锁下执行,我们仍然期待各实现检验每个块内的代码,并为事务安全代码使用乐观并发(在可用时以硬件事务性内存为后盾),为非事务安全代码使用最小锁定。当同步块调用非内联函数时,除非该函数声明为 transaction_safe
(见下文)或使用 [[optimize_for_synchronized]]
属性(见下文),否则编译器可能必须放弃推测执行(spculative execution),并在整个调用周围持有一个锁。
原子块
本节未完成 |
atomic_noexcept
复合语句
atomic_cancel
复合语句
atomic_commit
复合语句
用于 atomic_cancel
块中的事务取消的异常有 std::bad_alloc、std::bad_array_new_length、std::bad_cast、std::bad_typeid、std::bad_exception、std::exception 和所有从它派生的标准库异常,以及特殊异常类型 std::tx_exception<T>。
不允许原子块中的 复合语句 执行任何非 transaction_safe
的表达式或语句,或调用非 transaction_safe
的函数(这是编译时错误)。
// 每次调用 f() 都取得 i 的唯一值,即使以并行进行 int f() { static int i = 0; atomic_noexcept { // 开始事务 // printf("before %d\n", i); // 错误:不能调用非事务安全的函数 ++i; return i; // 提交事务 } }
以除异常之外的任何方式(抵达结尾、goto、break、continue、return)离开原子块时,将提交事务。若用 std::longjmp 退出原子块则行为未定义。
事务安全的函数
本节未完成 |
可在函数声明中用关键词 transaction_safe 将其显式声明为事务安全。
本节未完成 |
mutable
之后(若使用它)出现。
本节未完成 |
extern volatile int * p = 0; struct S { virtual ~S(); }; int f() transaction_safe { int x = 0; // OK:非 volatile p = &x; // OK:指针非 volatile int i = *p; // 错误:通过 volatile 泛左值读取 S s; // 错误:调用不安全的析构函数 }
int f(int x) { // 隐式事务安全 if (x <= 0) return 0; return x + f(x-1); }
若通过指向事务安全函数的引用或指针调用非事务安全的函数,则行为未定义。
指向事务安全函数的指针和指向事务安全成员函数的指针分别可隐式转换为函数指针和成员函数指针。结果指针和原指针是否比较相等是未指明的。
事务安全的虚函数
本节未完成 |
若 transaction_safe_dynamic
函数的最终覆盖函数未被声明为 transaction_safe
,则在原子块中调用它是未定义行为。
标准库
除了引入新的异常模板 std::tx_exception 之外,事务性内存技术规范还对标准库做出下列更改:
- 令下列函数为显式
transaction_safe
:
- std::forward、std::move、std::move_if_noexcept、std::align、std::abort、全局默认 operator new、全局默认 operator delete、std::allocator::construct(若调用的构造函数为事务安全)、std::allocator::destroy(若调用的析构函数为事务安全)、std::get_temporary_buffer、std::return_temporary_buffer、std::addressof、std::pointer_traits::pointer_to、所有支持事务取消的异常类型(见上文的
atomic_cancel
)的每个非虚成员函数本节未完成
原因:还有更多
- std::forward、std::move、std::move_if_noexcept、std::align、std::abort、全局默认 operator new、全局默认 operator delete、std::allocator::construct(若调用的构造函数为事务安全)、std::allocator::destroy(若调用的析构函数为事务安全)、std::get_temporary_buffer、std::return_temporary_buffer、std::addressof、std::pointer_traits::pointer_to、所有支持事务取消的异常类型(见上文的
- 令下列函数为显式
transaction_safe_dynamic
- 所有支持事务取消的异常类型(见上文的
atomic_cancel
)的每个虚函数
- 所有支持事务取消的异常类型(见上文的
- 要求分配器 (Allocator) X 上所有事务安全的操作在
X::rebind<>::other
上亦为事务安全
属性
[[optimize_for_synchronized]]
属性可应用到函数声明中的声明符中,而且必须在函数的首个声明上出现。
若某函数在一个翻译单元中被声明为 [[optimize_for_synchronized]]
,而同一个函数在另一翻译单元中的声明不带 [[optimize_for_synchronized]]
,则程序非良构;不要求诊断。
它指示函数定义应该针对从 synchronized 语句中调用而优化。具体而言,若函数对大多数的调用但非全部调用为事务安全(例如可能必须重算散列的哈希表插入、可能必须请求新内存块的分配器、可能罕有记录日志的简单函数),则该属性避免对调用该函数的同步块进行串行化。
std::atomic<bool> rehash{false}; // 维护线程运行此循环 void maintenance_thread(void*) { while (!shutdown) { synchronized { if (rehash) { hash.rehash(); rehash = false; } } } } // 工作线程每秒执行数十万次对此函数的调用。 // 从另一翻译单元中的同步块调用 insert_key() 将导致这些块被串行化, // 除非标记 insert_key() 为 [[optimize_for_synchronized]] [[optimize_for_synchronized]] void insert_key(char* key, char* value) { bool concern = hash.insert(key, value); if (concern) rehash = true; }
无该属性的 GCC 汇编:串行化整个函数
insert_key(char*, char*): subq $8, %rsp movq %rsi, %rdx movq %rdi, %rsi movl $hash, %edi call Hash::insert(char*, char*) testb %al, %al je .L20 movb $1, rehash(%rip) mfence .L20: addq $8, %rsp ret
有该属性的 GCC 汇编:
transaction clone for insert_key(char*, char*): subq $8, %rsp movq %rsi, %rdx movq %rdi, %rsi movl $hash, %edi call transaction clone for Hash::insert(char*, char*) testb %al, %al je .L27 xorl %edi, %edi call _ITM_changeTransactionMode # 注意:这是串行化点 movb $1, rehash(%rip) mfence .L27: addq $8, %rsp ret
本节未完成 原因:检查带主干的汇编,亦显示调用方的更改 |
注解
本节未完成 原因:来自 Wyatt 提案/讨论的体验笔记 |
编译器支持
GCC 从版本 6.1 起支持此技术规范(要求启用 -fgnu-tm)。GCC 4.7 时曾支持此规范的一个旧版变体。