命名空间
变体
操作

非静态数据成员

来自 cppreference.cn
< cpp‎ | 语言
 
 
C++ 语言
 
 

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

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
};

允许所有简单声明,除了

  • 不允许使用thread_local存储类说明符(但对于静态数据成员是允许的);
(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 起)

两个标准布局非联合类类型的公共初始序列是声明顺序中最长的非静态数据成员和位域序列,从每个类中的第一个此类实体开始,使得

  • 如果__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 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限定符(如果有)的相同类型,是布局兼容的枚举(即具有相同底层类型的枚举),或者如果它们的公共初始序列包含每个非静态数据成员和位域(在上面的示例中,AB是布局兼容的)。

两个标准布局联合被称为布局兼容,如果它们具有相同数量的非静态数据成员,并且对应的非静态数据成员(以任何顺序)具有布局兼容类型。

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

  • 在一个标准布局联合中,如果存在非联合类类型T1的活动成员,则允许读取另一个非联合类类型T2的联合成员m的非静态数据成员,前提是mT1T2的公共初始序列的一部分(通过非易失性左值读取易失性成员是未定义的除外)。
  • 指向标准布局类类型对象的指针可以被reinterpret_cast转换为指向其第一个非静态非位域数据成员的指针(如果它有非静态数据成员),否则转换为其任何基类子对象的指针(如果它有任何基类子对象),反之亦然。换句话说,标准布局类型的第一​​个数据成员之前不允许填充。请注意,严格别名规则仍然适用于此类转换的结果。
  • offsetof可用于确定任何成员相对于标准布局类开头的偏移量。

[编辑] 成员初始化

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

1) 在构造函数的成员初始化列表中。
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
}

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

(C++20 前)

数组类型的成员不能从成员初始化器中推断其大小

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 起)

[编辑] 用法

非静态数据成员或非静态成员函数的名称只能出现在以下三种情况中

1) 作为类成员访问表达式的一部分,其中该类具有此成员或派生自具有此成员的类,包括当非静态成员名称在允许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();
};
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; // OK: m in unevaluated operand
};
 
std::size_t j = sizeof(S::m + 42); // OK: even though there is no "this" object for m
注意:这些用法通过N2253中对CWG issue 613的解决而允许,某些编译器(例如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 指向没有数据成员的标准布局类的指针
可以重新解释转换为其第一个基类
可以重新解释转换为
其任何基类
CWG 2583 C++11 公共初始序列未考虑
对齐要求
已考虑
CWG 2759 C++20 公共初始序列可能包含
声明为[[no_unique_address]]的成员
它们不包含在内

[编辑] 另请参阅

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