名稱空間
變體
操作

非靜態資料成員

來自 cppreference.com
< 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]]的成員
它們不包含在內

[編輯] 另請參閱

靜態成員
非靜態成員函式
檢查型別是否為標準佈局型別
(類模板) [編輯]
標準佈局型別到指定成員的位元組偏移量
(函式宏) [編輯]