Namespaces
Variants

Non-static data members

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

非静态数据成员在类的 成员声明 中声明。

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;       // 两位位域
};

除以下情况外,允许任何 简单声明

(since C++11)
  • 不允许使用 不完整类型 抽象类类型 及其数组:特别地,类 C 不能拥有类型为 C 的非静态数据成员,但可以拥有类型为 C& (对 C 的引用)或 C* (指向 C 的指针)的非静态数据成员;
  • 若存在至少一个用户声明的构造函数,则非静态数据成员不能与类名相同;
  • 占位类型说明符(即 auto decltype ( auto ) (C++14 起) 推导 约束的类模板名 (C++17 起) 约束 的占位符 (C++20 起) )不能用于非静态数据成员声明(尽管对于 在类定义中初始化 的静态数据成员是允许的)。
(C++11 起)

此外,允许使用 位域 声明。

目录

布局

当某个类 C 的对象被创建时,每个非引用类型的非静态数据成员会被分配在 C 对象表示的某部分中。引用成员是否占用存储空间由实现定义,但其 存储期 与它们所属对象的存储期相同。

对于非 联合体 类类型, 非零大小 (C++20 起) 成员 未被 访问说明符 分隔 (C++11 前) 具有相同 成员访问权限 (C++11 起) 时,始终按声明顺序分配,使得后声明的成员在类对象中具有更高地址。 被访问说明符分隔 (C++11 前) 具有不同访问控制 (C++11 起) 的成员按未指定顺序分配(编译器可能将它们分组)。

(C++23 前)

对于非 联合体 类类型, 非零大小 成员始终按声明顺序分配,使得后声明的成员在类对象中具有更高地址。注意成员访问控制仍会影响标准布局属性(见下文)。

(C++23 起)

对齐要求可能需要在类成员之间或最后一个成员之后添加填充。

标准布局

当且仅当类为 POD类 时,该类被视为具有标准布局并满足下述特性。

(C++11 前)

当所有非静态数据成员具有相同访问控制且满足特定其他条件时,该类被称为标准布局类(完整要求列表参见 标准布局类 )。

(C++11 起)

两个标准布局非联合类类型的 公共起始序列 是指:从每个类的首个非静态数据成员或位域开始,按声明顺序构成的最长非静态数据成员与位域序列,且满足以下条件:

  • __has_cpp_attribute ( no_unique_address ) 不为 0 ,则两个实体均未声明 [[ no_unique_address ]] 属性,
(C++20 起)
  • 对应实体具有布局兼容的类型,
  • 对应实体具有相同的 对齐要求 ,且
  • 两个实体要么都是具有相同宽度的位域,要么都不是位域。
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限定符(若存在)后的相同类型,是布局兼容的 枚举 (即具有相同底层类型的枚举),或者它们的 公共初始序列 包含所有非静态数据成员和位域(在上例中, A B 是布局兼容的)。

两个标准布局联合体若满足以下条件则称为 布局兼容 :它们具有相同数量的非静态数据成员,且对应的非静态数据成员(以任意顺序)具有布局兼容的类型。

标准布局类型具有以下特殊属性:

  • 在具有非联合类类型 T1 活跃成员的标准布局联合体中,允许读取另一个非联合类类型 T2 联合体成员的非静态数据成员 m ,前提是 m 属于 T1 T2 的公共起始序列(但通过非 volatile 泛左值读取 volatile 成员是未定义行为)。
  • 指向标准布局类类型对象的指针可以被 reinterpret_cast 转换为其第一个非静态非位域数据成员(如果存在非静态数据成员)或其任何基类子对象(如果存在基类)的指针,反之亦然。换言之,标准布局类型的第一个数据成员之前不允许存在填充。注意 严格别名 规则仍适用于此类转换结果。
  • offsetof 可用于确定标准布局类中任意成员相对于类起始位置的偏移量。

成员初始化

非静态数据成员可以通过以下两种方式之一进行初始化:

1) 在构造函数的 成员初始化列表 中。
struct S
{
    int n;
    std::string s;
    S() : n(7) {} // 直接初始化 n,默认初始化 s
};
2) 通过 默认成员初始化器 ,即包含在成员声明中的花括号或等号 初始化器 ,当该成员在构造函数的成员初始化列表中被省略时使用。
struct S
{
    int n = 7;
    std::string s{'a', 'b', 'c'};
    S() {} // 默认成员初始化器将拷贝初始化 n,列表初始化 s
};

若成员同时拥有默认成员初始化器且在构造函数的成员初始化列表中出现,则对该构造函数忽略默认成员初始化器。

#include <iostream>
int x = 0;
struct S
{
    int n = ++x;
    S() {}                 // 使用默认成员初始化器
    S(int arg) : n(arg) {} // 使用成员初始化器
};
int main()
{
    std::cout << x << '\n'; // 输出 0
    S s1;                   // 默认初始化器已执行
    std::cout << x << '\n'; // 输出 1
    S s2(7);                // 默认初始化器未执行
    std::cout << x << '\n'; // 输出 1
}

不允许对 位域 成员使用默认成员初始化器。

(C++20 前)

数组类型成员无法从成员初始化器推导其大小:

struct X
{
    int a[] = {1, 2, 3};  // 错误
    int b[3] = {1, 2, 3}; // 正确
};

默认成员初始化器不允许导致隐式定义外围类的默认 默认构造函数 或该构造函数的异常规范:

struct node
{
    node* p = new node; // 错误:使用了隐式或默认的 node::node()
};

在默认成员初始化器中,引用成员不能绑定到临时量(注意: 成员初始化器列表 存在相同规则):

struct A
{
    A() = default;     // 正确
    A(int v) : v(v) {} // 正确
    const int& v = 42; // 正确
};
A a1;    // 错误:将临时量绑定到引用无效
A a2(1); // 正确(因 v 出现在构造函数中,默认成员初始化器被忽略)
         // 但 a2.v 是悬垂引用
(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
(since C++17)

用法

非静态数据成员或非静态成员函数的名称仅可出现在以下三种情形中:

1) 作为类成员访问表达式的一部分,该类要么拥有该成员,要么派生自拥有该成员的类,包括在允许使用 this 的任何上下文中(成员函数体内、成员初始化列表、类内默认成员初始化器中)使用非静态成员名称时出现的隐式 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();
};
2) 用于形成 指向非静态成员的指针
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
3) (仅适用于数据成员,不适用于成员函数)当用于 未求值操作数 时。
struct S
{
    int m;
    static const std::size_t sz = sizeof m; // 正确:m 位于未求值操作数中
};
std::size_t j = sizeof(S::m + 42); // 正确:即使 m 没有对应的 "this" 对象
注:此类用法通过 CWG issue 613 N2253 中的决议被允许,某些编译器(如 clang)将其视为 C++11 的变更。

注释

功能测试宏 标准 功能特性
__cpp_nsdmi 200809L (C++11) 非静态数据成员初始化器
__cpp_aggregate_nsdmi 201304L (C++14) 具有 默认成员初始化器 聚合类

缺陷报告

以下行为变更缺陷报告被追溯应用于先前发布的C++标准。

缺陷报告 应用于 发布时的行为 正确行为
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 ]] 的成员
不包含此类成员

参见

静态成员
非静态成员函数
检查类型是否为 标准布局 类型
(类模板)
标准布局 类型起始到指定成员的字节偏移量
(函数宏)