非静态数据成员
非静态数据成员在类的成员规范中声明。
class S { int n; // non-static data member int& r; // non-static data member of reference type int a[2] = {1, 2}; // non-static data member with default member initializer (C++11) std::string s, *ps; // two non-static data members struct NestedS { std::string s; } d5; // non-static data member of nested type char bit : 2; // two-bit bitfield };
允许任何简单声明,除了
|
(自 C++11 起) |
- 不允不完整类型、抽象类类型及其数组: 特别是,类
C
不能具有类型为C
的非静态数据成员,但它可以具有类型为C&
(对 C 的引用)或C*
(指向C
的指针)的非静态数据成员; - 如果存在至少一个用户声明的构造函数,则非静态数据成员不能与类的名称同名;
(自 C++11 起) |
此外,允许位域声明。
目录 |
[编辑] 布局
当创建某个类 C
的对象时,每个非引用类型的非静态数据成员都会在 C
的对象表示的某个部分中分配。 引用成员是否占用任何存储空间是实现定义的,但它们的存储持续时间与它们作为成员的对象相同。
对于非联合体类类型,非零大小的(自 C++20 起) 成员 不由访问说明符分隔(直到 C++11)具有相同成员访问权限的成员(自 C++11 起) 总是以这样的方式分配,即稍后声明的成员在类对象中具有更高的地址。 由访问说明符分隔的成员(直到 C++11)具有不同访问控制的成员(自 C++11 起) 以未指定的顺序分配(编译器可能会将它们分组在一起)。 |
(直到 C++23) |
对于非联合体类类型,非零大小的成员总是以这样的方式分配,即稍后声明的成员在类对象中具有更高的地址。 请注意,成员的访问控制仍然会影响标准布局属性(见下文)。 |
(自 C++23 起) |
对齐要求可能需要在成员之间或类中的最后一个成员之后进行填充。
[编辑] 标准布局
如果一个类是 POD 类,则该类被认为是标准布局并具有以下描述的属性。 |
(直到 C++11) |
所有非静态数据成员都具有相同的访问控制并且满足某些其他条件的类被称为标准布局类(有关要求列表,请参见标准布局类)。 |
(自 C++11 起) |
两个标准布局非联合体类类型的公共初始序列是以声明顺序排列的最长非静态数据成员和位域序列,从每个类中的第一个此类实体开始,使得
|
(自 C++20 起) |
- 对应的实体具有布局兼容的类型,
- 对应的实体具有相同的对齐要求,并且
- 要么两个实体都是具有相同宽度的位域,要么都不是位域。
struct A { int a; char b; }; struct B { const int b1; volatile char b2; }; // A and B's common initial sequence is A.a, A.b and B.b1, B.b2 struct C { int c; unsigned : 0; char b; }; // A and C's common initial sequence is A.a and C.c struct D { int d; char b : 4; }; // A and D's common initial sequence is A.a and D.d struct E { unsigned int e; char b; }; // A and E's common initial sequence is empty
如果两个标准布局非联合体类类型是相同的类型(忽略 cv 限定符,如果有的话),是布局兼容的枚举(即具有相同底层类型的枚举),或者如果它们的公共初始序列由每个非静态数据成员和位域组成(在上面的示例中,A
和 B
是布局兼容的),则称它们为布局兼容。
如果两个标准布局联合体具有相同数量的非静态数据成员,并且对应的非静态数据成员(以任何顺序)具有布局兼容的类型,则称它们为布局兼容。
标准布局类型具有以下特殊属性
- 在具有非联合体类类型
T1
的活动成员的标准布局联合体中,允许读取另一个非联合体类类型T2
的联合体成员的非静态数据成员m
,前提是m
是T1
和T2
的公共初始序列的一部分(除非通过非 volatile 左值读取 volatile 成员是未定义的)。 - 标准布局类类型的对象的指针可以
reinterpret_cast
转换为指向其第一个非静态非位域数据成员的指针(如果它具有非静态数据成员),否则可以转换为其任何基类子对象的指针(如果它有任何基类子对象),反之亦然。 换句话说,标准布局类型的第一个数据成员之前不允许填充。 请注意,严格别名规则仍然适用于此类转换的结果。 - 宏 offsetof 可用于确定任何成员相对于标准布局类开头的偏移量。
- 在具有非联合体类类型
[编辑] 成员初始化
非静态数据成员可以通过以下两种方式之一进行初始化
struct S { int n; std::string s; S() : n(7) {} // direct-initializes n, default-initializes s };
2) 通过默认成员初始化器,它是成员声明中包含的大括号或等号初始化器,并且如果构造函数的成员初始化列表中省略了该成员,则使用该初始化器。
struct S { int n = 7; std::string s{'a', 'b', 'c'}; S() {} // default member initializer will copy-initialize n, list-initialize s }; 如果成员具有默认成员初始化器,并且也出现在构造函数中的成员初始化列表中,则对于该构造函数,将忽略默认成员初始化器。 运行此代码 #include <iostream> int x = 0; struct S { int n = ++x; S() {} // uses default member initializer S(int arg) : n(arg) {} // uses member initializer }; int main() { std::cout << x << '\n'; // prints 0 S s1; // default initializer ran std::cout << x << '\n'; // prints 1 S s2(7); // default initializer did not run std::cout << x << '\n'; // prints 1 }
数组类型的成员无法从成员初始化器推导出其大小 struct X { int a[] = {1, 2, 3}; // error int b[3] = {1, 2, 3}; // OK }; 不允许默认成员初始化器导致隐式定义封闭类的默认默认构造函数或该构造函数的异常规范 struct node { node* p = new node; // error: use of implicit or defaulted node::node() }; 引用成员不能在默认成员初始化器中绑定到临时对象(注意:成员初始化列表也存在相同的规则) struct A { A() = default; // OK A(int v) : v(v) {} // OK const int& v = 42; // OK }; A a1; // error: ill-formed binding of temporary to reference A a2(1); // OK (default member initializer ignored because v appears in a constructor) // however a2.v is a dangling reference |
(自 C++11 起) |
如果从其默认成员初始化器初始化引用成员(直到 C++20)成员具有默认成员初始化器(自 C++20 起),并且它的潜在求值子表达式是聚合初始化,该聚合初始化将使用该默认成员初始化器,则程序是非良构的 struct A; extern A a; struct A { const A& a1{A{a, a}}; // OK const A& a2{A{}}; // error }; A a{a, a}; // OK |
(自 C++17 起) |
[编辑] 用法
非静态数据成员或非静态成员函数的名称只能在以下三种情况下出现
this
的任何上下文(在成员函数体中、在成员初始化列表中、在类内默认成员初始化器中)中使用非静态成员名称时出现的隐式 this-> 成员访问表达式。struct S { int m; int n; int x = m; // OK: implicit this-> allowed in default initializers (C++11) S(int i) : m(i), n(m) // OK: implicit this-> allowed in member initializer lists { this->f(); // explicit member access expression f(); // implicit this-> allowed in member function bodies } void f(); };
struct S { int m; void f(); }; int S::*p = &S::m; // OK: use of m to make a pointer to member void (S::*fp)() = &S::f; // OK: use of f to make a pointer to member
struct S { int m; static const std::size_t sz = sizeof m; // OK: m in unevaluated operand }; std::size_t j = sizeof(S::m + 42); // OK: even though there is no "this" object for m
[编辑] 注释
特性测试宏 | 值 | 标准 | 特性 |
---|---|---|---|
__cpp_nsdmi |
200809L |
(C++11) | 非静态数据成员初始化器 |
__cpp_aggregate_nsdmi |
201304L |
(C++14) | 聚合类具有默认成员初始化器 |
[编辑] 缺陷报告
以下行为变更缺陷报告被追溯应用于先前发布的 C++ 标准。
DR | 应用于 | 已发布行为 | 正确行为 |
---|---|---|---|
CWG 80 | C++98 | 所有数据成员不能具有相同的名称 作为类的名称(破坏 C 兼容性) |
允许非静态数据成员 共享类名,如果 没有用户声明的构造函数 |
CWG 190 | C++98 | 在确定布局兼容性时, 所有成员都被考虑 |
仅考虑非 静态数据成员 |
CWG 613 | C++98 | 不允许未求值地使用非静态数据成员 | 允许此类用法 |
CWG 645 | C++98 | 位域和 非位域成员是否布局兼容未指定 |
不布局兼容 |
CWG 1397 | C++11 | 类被视为已完成 在默认成员初始化器中 |
默认成员初始化器不能触发 默认构造函数的定义 |
CWG 1425 | C++98 | 标准布局对象是否与第一个非静态 数据成员或第一个基类子对象共享相同的地址,这不清楚 数据成员或第一个基类子对象共享相同的地址,这不清楚 |
非静态数据成员 如果存在,否则为基 类子对象(如果存在) |
CWG 1696 | C++98 | 引用成员可以初始化为临时对象 (其生命周期将在构造函数末尾结束) |
此类初始化是非良构的 |
CWG 1719 | C++98 | 不同 cv 限定类型的类型不布局兼容 | 忽略 cv 限定符,改进规范 |
CWG 2254 | C++11 | 没有数据成员的标准布局类的指针 成员可以 reinterpret_cast 到其第一个基类 |
可以 reinterpret_cast 到其任何基类 |
CWG 2583 | C++11 | 公共初始序列没有 考虑对齐要求 |
已考虑 |
CWG 2759 | C++20 | 公共初始序列可以包括 声明为 [[no_unique_address]] 的成员 |
它们不包括在内 |
[编辑] 参见
类 | |
静态成员 | |
非静态成员函数 | |
(C++11) |
检查类型是否为标准布局类型 (类模板) |
从标准布局类型的开头到指定成员的字节偏移量 (函数宏) |