非静态数据成员

来自cppreference.com
< cpp‎ | language

非静态数据成员声明于类的成员指定

class S
{
    int n;                // 非静态数据成员
    int& r;               // 引用类型的非静态数据成员
    int a[10] = {1, 2};   // 带初始化器的非静态数据成员 (C++11)
    std::string s, *ps;   // 二个非静态数据成员
    struct NestedS {
        std::string s;
    } d5, *d6;            // 二个嵌套类型的非静态数据成员
    char bit : 2;         // 2 位的位域
};

允许任何简单声明,除了

  • 不允许 externregister 存储类指定符;
  • 不允许 thread_local 存储类指定符(但对于 static 数据成员允许);
  • 不允许不完整类型:通常,类 C 不能拥有类型 C 的非静态数据成员,尽管它能拥有 C& (到 C 的引用)或 C* (指向 C 的指针)类型的非静态数据成员;
  • 非静态数据成员不能拥有与类名相同的名称,若存在至少一个用户定义的构造函数;
  • auto 指定符不能用于非静态数据成员你生命(尽管对于于类定义内初始化的静态数据成员是允许)的。

另外,允许位域声明

目录

[编辑] 布局

创建某个类 C 的对象时,每个非引用类型的非静态数据成员被分配于 C 的对象表示的某个位置。引用是否占据任何存储是实现定义的,但其存储期与它们作为成员存在于其中的对象相同。

对于非联合体类类型,拥有同一成员访问的成员始终按照较后声明的成员在类对象中拥有较高地址的方式分配。拥有不同访问控制的成员以未指定顺序分配(编译器可以将它们组合在一起)。对齐要求可能需要在成员间,或在类的最后成员后填充。

[编辑] 标准布局

其中所有非静态数据成员拥有相同访问控制,且满足某些条件的类被称作标准布局类型(要求列表见标准布局类型 (StandardLayoutType) )。

二个标准布局非联合类类型可以拥有非静态数据成员和位域 (C++14 起)公共起始序列,其为一或多个起始成员(按声明顺序)的序列,若成员拥有布局兼容类型,且若它们是位域,则拥有相同宽度 (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 起)组成,则称它们布局兼容(上例中, AB 布局兼容)

若二个标准布局联合体类型拥有相同数量的非静态数据成员,且(以任何顺序)对应的非静态数据成员拥有布局兼容的类型,则称它们布局兼容

标准布局类型拥有下列特殊属性:

  • 若标准布局联合体保有二个(或更多)标准布局结构体为成员,且这些结构体拥有数据成员的公共起始序列,则检测该公共起始序列的任意成员都是良好定义的,无关乎联合体的哪个成员活跃。
(C++14 前)
  • 在拥有非联合类类型 T1 作为活跃成员的标准布局联合体中,容许读取拥有非联合类类型 T2 的另一联合体成员的非静态数据成员 m ,只要 mT1T2 的公共起始序列的一部分(除了通过非 volatile 泛左值读取 volatile 成员是未定义的)。
(C++14 起)
  • 指向标准布局结构体类型的指针能 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 (默认初始化器不运行)
}

输出:

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

[编辑] 用法

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

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 : 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 相等,提升规格

[编辑] 参阅