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++ 关键词
非静态数据成员
非静态数据成员是在类的成员说明中声明的。
class S { int n; // 非静态数据成员 int& r; // 引用类型的非静态数据成员 int a[2] = {1, 2}; // 带默认成员初始化器的非静态数据成员 (C++11) std::string s, *ps; // 两个非静态数据成员 struct NestedS { std::string s; } d5; // 具有嵌套类型的非静态数据成员 char bit : 2; // 2 位的位域 };
允许任何简单声明,但
- 不允许使用 extern 和 register 存储类说明符;
- 不允许使用 thread_local 存储类说明符(但允许 static 数据成员);
- 不允许不完整类型、抽象类类型及其数组:通常,类
C
不能拥有C
类型的非静态数据成员,尽管它能拥有C&
(C 的引用)或C*
(C 的指针)类型的非静态数据成员; - 当存在至少一个用户定义的构造函数时,非静态数据成员不能拥有与类名相同的名字;
- auto 说明符不能用于非静态数据成员的声明(尽管对于于类定义中初始化的静态数据成员是允许的)。
另外,允许位域声明。
布局
当创建某个类 C
的对象时,每个非引用类型的非静态数据成员都被分配于 C
的对象表示的某个部分之中。引用是否占据任何存储是由实现定义的,但其存储期与以它们作为成员的对象相同。
对于非联合体类类型,拥有相同成员访问和非零大小 (C++20 起)的成员,始终按照较后声明的成员在类对象中拥有较高地址的方式分配。拥有不同访问控制的成员以未指明的顺序分配(编译器可以将它们组合在一起)。对齐要求可能需要在成员间,或在类的最后成员之后进行填充。
标准布局
所有非静态数据成员均拥有相同访问控制,且满足其他特定条件的类被称作标准布局(standard layout)类型(对其规定的列表见标准布局类型 (StandardLayoutType) )。
两个标准布局非联合类类型可以拥有非静态数据成员和位域 (C++14 起)的共同起始序列,其为一或多个起始成员(按声明顺序)的序列,条件是这些成员拥有布局兼容的类型,均声明有 [[no_unique_address]]
属性或无该属性 (C++20 起),且当它们是位域时拥有相同宽度 (C++14 起)。
struct A { int a; char b; }; struct B { const int b1; volatile char b2; }; // A 与 B 的共同起始序列是 A.a, A.b 与 B.b1, B.b2 struct C { int c; unsigned : 0; char b; }; // A 与 C 的共同起始序列是 A.a 与 C.c struct D { int d; char b : 4; }; // A 与 D 的共同起始序列是 A.a 与 D.d struct E { unsigned int e; char b; }; // A 与 E 的共同起始序列为空
若两个标准布局非联合类类型具有同一类型(若存在 cv 限定符则忽略) (C++14 起),或是布局兼容的枚举(即拥有相同底层类型的枚举类型),或其共同起始序列由其所有非静态数据成员和位域 (C++14 起)组成,则称它们布局兼容(layout compatible)(上例中,A
与 B
布局兼容)
若两个标准布局联合体类型拥有相同数量的非静态数据成员,且(以任何顺序)对应的非静态数据成员拥有布局兼容的类型,则称它们布局兼容。
标准布局类型拥有以下特殊性质:
|
(C++14 前) |
|
(C++14 起) |
- 指向标准布局类类型的指针可被
reinterpret_cast
成指向其首个非静态非位域数据成员的指针(若它拥有非静态数据成员),或指向其任何基类子对象的指针(若有基类),反之亦然。换言之,不允许标准布局类型的首个数据成员前有填充。注意严格别名化规则仍然适用于这种转型的结果。 - 宏 offsetof 可用于确定任何成员距标准布局类起始的偏移量。
- 指向标准布局类类型的指针可被
成员初始化
非静态数据成员可以用下列两种方式之一初始化:
2) 通过默认成员初始化器,它是包含于成员声明中的花括号或等号初始化器,并在成员初始化器列表中忽略该成员的情况下得到使用
struct S { int n = 7; std::string s{'a', 'b', 'c'}; S() // 默认成员初始化器将复制初始化 n ,列表初始化 s { } }; 若成员拥有默认成员初始化器,并且亦出现于构造函数的成员初始化器列表中,则对该构造函数忽略默认成员初始化器。 运行此代码 输出: 0 1 1
数组类型成员不能从成员初始化器推导其大小: struct X { int a[] = {1,2,3}; // 错误 int b[3] = {1,2,3}; // OK }; 默认成员初始化器不允许导致外围类的预置默认构造函数的隐式定义,或该构造函数的异常说明: struct node { node* p = new node; // 错误:使用隐式或预置的 node::node() }; |
(C++11 起) | ||
在默认成员初始化器中,引用成员不能绑定到临时量(注意,成员初始化器列表有同样的规则) struct A { A() = default; // OK A(int v) : v(v) { } // OK const int& v = 42; // OK }; A a1; // 错误:临时量到引用的非良构绑定 A a2(1); // OK(忽略默认成员初始化器,因为 v 出现于构造函数中) // 然而 a2.v 是悬垂引用 若默认成员初始化器拥有会执行聚合初始化并会使用同一初始化器的子表达式,则产生错误: struct A; extern A a; struct A { const A& a1{ A{a, a} }; // OK const A& a2{ A{} }; // 错误 }; A a{a, a}; // OK |
(C++14 起) |
用法
非静态数据成员或非静态成员函数的名字只能出现在下列三种情形中:
this->
成员访问表达式。
struct S { int m; int n; int x = m; // OK:在默认初始化器中允许隐式的 this-> (C++11) S(int i) : m(i), n(m) // OK:在成员初始化器列表中允许隐式的 this-> { this->f(); // 显式的成员访问表达式 f(); // 在成员函数体内允许隐式 this-> } void f(); };
struct S { int m; void f(); }; int S::*p = &S::m; // OK:m 用于构成成员指针 void (S::*fp)() = &S::f; // OK:f 用于构成成员指针
3) (仅对数据成员,而非成员函数)当在不求值操作数中使用时。
struct S { int m; static const std::size_t sz = sizeof m; // OK:不求值操作数中的 m }; std::size_t j = sizeof(S::m + 42); // OK:即便没有 m 的 "this" 对象 |
(C++03 起) |
缺陷报告
下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。
DR | 应用于 | 出版时的行为 | 正确行为 |
---|---|---|---|
CWG 613 | C++03 | 不允许非静态数据成员的不求值使用 | 允许这种使用 |
CWG 1696 | C++14 | 引用成员能初始化为临时量(其生存期会在构造函数结束时结束) | 这种初始化非良构 |
CWG 1397 | C++11 | 在默认成员初始化器中类被视为完整的 | 默认成员初始化不能触发默认构造函数的定义 |
CWG 1719 | C++14 | cv 限定有所不同的类型曾经不是布局兼容的 | 忽略 cv 限定,改进规范 |
CWG 2254 | C++14 | 指向无数据成员的标准布局类的指针能 reinterpret_cast 到其首个基类 | 能 reinterpret_cast 到其任何基类 |