非静态数据成员
非静态数据成员在类的 成员说明 中声明。
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
的每个非静态数据成员分配一些存储空间。引用成员是否占用任何存储空间是实现定义的,但它们的 存储持续时间 与它们所在的类的对象的存储持续时间相同。
对于非 联合 类类型,非零大小的(从 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
的共同初始序列的一部分(除了通过非易失性左值读取易失性成员是未定义的行为)。 - 指向标准布局类类型对象的指针可以
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
(在成员函数体中,在成员初始化列表中,在类内默认成员初始化器中)。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
[编辑] 说明
功能测试宏 | 值 | Std | 功能 |
---|---|---|---|
__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) |
检查一个类型是否是标准布局 类型 (类模板) |
从标准布局 类型的开头到指定成员的字节偏移量 (函数宏) |