簡體   English   中英

為什么我不能在類中初始化非常量靜態成員或靜態數組?

[英]Why can't I initialize non-const static member or static array in class?

為什么我不能在類中初始化非常量static成員或static數組?

class A
{
    static const int a = 3;
    static int b = 3;
    static const int c[2] = { 1, 2 };
    static int d[2] = { 1, 2 };
};

int main()
{
    A a;

    return 0;
}

編譯器發出以下錯誤:

g++ main.cpp
main.cpp:4:17: error: ISO C++ forbids in-class initialization of non-const static member ‘b’
main.cpp:5:26: error: a brace-enclosed initializer is not allowed here before ‘{’ token
main.cpp:5:33: error: invalid in-class initialization of static data member of non-integral type ‘const int [2]’
main.cpp:6:20: error: a brace-enclosed initializer is not allowed here before ‘{’ token
main.cpp:6:27: error: invalid in-class initialization of static data member of non-integral type ‘int [2]’

我有兩個問題:

  1. 為什么我不能在類中初始化static數據成員?
  2. 為什么我不能在類中初始化static數組,甚至是const數組?

為什么我不能在類中初始化static數據成員?

C++ 標准只允許在類內部初始化靜態常量整數或枚舉類型。 這就是a可以被初始化而其他人不能被初始化的原因。

參考:
C++03 9.4.2 靜態數據成員
§4

如果靜態數據成員是 const 整型或 const 枚舉類型,則它在類定義中的聲明可以指定一個常量初始化器,它應該是一個整型常量表達式 (5.19)。 在這種情況下,成員可以出現在整數常量表達式中。 如果在程序中使用該成員,則該成員仍應在名稱空間范圍內定義,並且名稱空間范圍定義不應包含初始化程序。

什么是整數類型?

C++03 3.9.1 基本類型
§7

類型 bool、char、wchar_t 以及有符號和無符號整數類型統稱為整數類型。43) 整數類型的同義詞是整數類型。

腳注:

43)因此,枚舉 (7.2) 不是整數; 但是,枚舉可以提升為 int、unsigned int、long 或 unsigned long,如 4.5 中所指定。

解決方法:

您可以使用枚舉技巧在您的類定義中初始化一個數組。

class A 
{
    static const int a = 3;
    enum { arrsize = 2 };

    static const int c[arrsize] = { 1, 2 };

};

為什么標准不允許這樣做?

Bjarne在這里恰當地解釋了這一點

類通常在頭文件中聲明,而頭文件通常包含在許多翻譯單元中。 但是,為了避免復雜的鏈接器規則,C++ 要求每個對象都有唯一的定義。 如果 C++ 允許需要作為對象存儲在內存中的實體的類內定義,那么這條規則就會被打破。

為什么只允許static const整數類型和枚舉類內初始化?

答案隱藏在 Bjarne 的引文中仔細閱讀,
“C++ 要求每個對象都有一個唯一的定義。如果 C++ 允許需要作為對象存儲在內存中的實體的類內定義,那么這條規則就會被打破。”

請注意,只有static const整數才能被視為編譯時常量。 編譯器知道整數值不會在任何時候改變,因此它可以應用自己的魔法並應用優化,編譯器簡單地內聯這些類成員,即它們不再存儲在內存中,因為不需要存儲在內存中,它使這些變量成為 Bjarne 提到的規則的例外。

這里值得注意的是,即使static const整數值可以進行類內初始化,也不允許對此類變量取地址。 如果(且僅當)靜態成員具有類外定義,則可以獲取靜態成員的地址。這進一步驗證了上述推理。

枚舉是允許的,因為枚舉類型的值可以用於需要整數的地方。 見上面的引文


這在 C++11 中有何變化?

C++11在一定程度上放寬了限制。

C++11 9.4.2 靜態數據成員
§3

如果靜態數據成員是 const 文字類型,則它在類定義中的聲明可以指定一個大括號或等號初始化器,其中每個作為賦值表達式的初始化器子句都是一個常量表達式。 可以在類定義中使用constexpr specifier;聲明文字類型的靜態數據成員constexpr specifier; 如果是這樣,它的聲明應指定一個大括號或等號初始化器,其中每個作為賦值表達式的初始化器子句都是一個常量表達式。 [ 注意:在這兩種情況下,成員都可能出現在常量表達式中。 —end note ] 如果在程序中使用該成員,則該成員仍應在名稱空間范圍內定義,並且名稱空間范圍定義不應包含初始化程序。

此外,C++11允許(第 12.6.2.8 節)一個非靜態數據成員在它被聲明的地方(在它的類中)被初始化。 這將意味着更容易的用戶語義。

請注意,這些功能尚未在最新的 gcc 4.7 中實現,因此您可能仍然會遇到編譯錯誤。

這似乎是過去簡單鏈接器的遺物。 您可以在靜態方法中使用靜態變量作為解決方法:

// header.hxx
#include <vector>

class Class {
public:
    static std::vector<int> & replacement_for_initialized_static_non_const_variable() {
        static std::vector<int> Static {42, 0, 1900, 1998};
        return Static;
    }
};

int compilation_unit_a();

// compilation_unit_a.cxx
#include "header.hxx"

int compilation_unit_a() {  
    return Class::replacement_for_initialized_static_non_const_variable()[1]++;
}

// main.cxx
#include "header.hxx"

#include <iostream>

int main() {
    std::cout
    << compilation_unit_a()
    << Class::replacement_for_initialized_static_non_const_variable()[1]++
    << compilation_unit_a()
    << Class::replacement_for_initialized_static_non_const_variable()[1]++
    << std::endl;
}

建造:

g++ -std=gnu++0x -save-temps=obj -c compilation_unit_a.cxx 
g++ -std=gnu++0x -o main main.cxx compilation_unit_a.o

跑步:

./main

這有效的事實(始終如一,即使類定義包含在不同的編譯單元中)表明今天的鏈接器(gcc 4.9.2)實際上已經足夠聰明了。

有趣:在手臂上打印0123 ,在 x86 上打印3210

這是因為所有翻譯單元都只能使用一個A::a定義。

如果你執行了static int a = 3; 在包含在所有翻譯單元中的標題中的類中,您將獲得多個定義。 因此,靜態的非外聯定義強行導致編譯器錯誤。

使用static inlinestatic const解決這個問題。 如果它在翻譯單元中使用, static inline僅將符號具體化,並確保鏈接器僅選擇並保留一個副本,如果它在多個翻譯單元中定義,因為它在一個 comdat 組中。 文件范圍內的const使編譯器永遠不會發出符號,因為它總是在代碼中立即被替換,除非使用了extern ,這在類中是不允許的。

需要注意的一件事是static inline int b; 被視為定義,而static const int bstatic const A b; 仍然被視為聲明,如果您不在類中定義它,則必須在外定義它。 有趣的是static constexpr A b; 被視為定義,而static constexpr int b; 是一個錯誤並且必須有一個初始化器(這是因為它們現在變成了定義,並且像文件范圍內的任何 const/constexpr 定義一樣,它們需要一個初始化器,而 int 沒有但類類型有,因為它有一個隱式= A()當它是一個定義時——clang 允許這樣做,但 gcc 要求你顯式初始化,否則它是一個錯誤。這不是內聯的問題)。 static const A b = A(); 不允許並且必須是constexprinline以允許具有類類型的靜態對象的初始化程序,即使類類型的靜態成員不僅僅是聲明。 所以在某些情況下是的A a; 與顯式初始化A a = A(); (前者可以是聲明,但如果該類型只允許聲明,則后者是錯誤的。后者只能用於定義constexpr使其成為定義)。 如果您使用constexpr並指定默認構造函數,則構造函數將需要是constexpr

#include<iostream>

struct A
{
    int b =2;
    mutable int c = 3; //if this member is included in the class then const A will have a full .data symbol emitted for it on -O0 and so will B because it contains A.
    static const int a = 3;
};

struct B {
    A b;
    static constexpr A c; //needs to be constexpr or inline and doesn't emit a symbol for A a mutable member on any optimisation level
};

const A a;
const B b;

int main()
{
    std::cout << a.b << b.b.b;
    return 0;
}

靜態成員是一個完全的文件范圍聲明extern int A::a; (只能在類中進行,外線定義必須引用類中的靜態成員,並且必須是定義且不能包含 extern)而非靜態成員是類的完整類型定義的一部分,並且具有與沒有extern文件范圍聲明相同的規則。 它們是隱含的定義。 所以int i[]; int i[5]; int i[]; int i[5]; 是一個重新定義,而static int i[]; int A::i[5]; static int i[]; int A::i[5]; 不是但與 2 externs 不同的是,如果您執行static int i[]; static int i[5]; ,編譯器仍會檢測到重復的成員static int i[]; static int i[5]; static int i[]; static int i[5]; 在課堂里。

我認為這是為了防止您混淆聲明和定義。 (想想如果在多個地方包含文件可能會出現的問題。)

靜態變量特定於類。 構造函數特別為實例初始化屬性。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM