非静态数据成员
非静态数据成员是在类的成员说明中声明的。
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 到其任何基类 |