[英]c++ : why I can't assign a value to a non-const static member "inside" a class?
[英]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]’
我有兩個問題:
static
數據成員?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 };
};
類通常在頭文件中聲明,而頭文件通常包含在許多翻譯單元中。 但是,為了避免復雜的鏈接器規則,C++ 要求每個對象都有唯一的定義。 如果 C++ 允許需要作為對象存儲在內存中的實體的類內定義,那么這條規則就會被打破。
static const
整數類型和枚舉類內初始化? 答案隱藏在 Bjarne 的引文中仔細閱讀,
“C++ 要求每個對象都有一個唯一的定義。如果 C++ 允許需要作為對象存儲在內存中的實體的類內定義,那么這條規則就會被打破。”
請注意,只有static const
整數才能被視為編譯時常量。 編譯器知道整數值不會在任何時候改變,因此它可以應用自己的魔法並應用優化,編譯器簡單地內聯這些類成員,即它們不再存儲在內存中,因為不需要存儲在內存中,它使這些變量成為 Bjarne 提到的規則的例外。
這里值得注意的是,即使static const
整數值可以進行類內初始化,也不允許對此類變量取地址。 如果(且僅當)靜態成員具有類外定義,則可以獲取靜態成員的地址。這進一步驗證了上述推理。
枚舉是允許的,因為枚舉類型的值可以用於需要整數的地方。 見上面的引文
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 inline
或static const
解決這個問題。 如果它在翻譯單元中使用, static inline
僅將符號具體化,並確保鏈接器僅選擇並保留一個副本,如果它在多個翻譯單元中定義,因為它在一個 comdat 組中。 文件范圍內的const
使編譯器永遠不會發出符號,因為它總是在代碼中立即被替換,除非使用了extern
,這在類中是不允許的。
需要注意的一件事是static inline int b;
被視為定義,而static const int b
或static const A b;
仍然被視為聲明,如果您不在類中定義它,則必須在外定義它。 有趣的是static constexpr A b;
被視為定義,而static constexpr int b;
是一個錯誤並且必須有一個初始化器(這是因為它們現在變成了定義,並且像文件范圍內的任何 const/constexpr 定義一樣,它們需要一個初始化器,而 int 沒有但類類型有,因為它有一個隱式= A()
當它是一個定義時——clang 允許這樣做,但 gcc 要求你顯式初始化,否則它是一個錯誤。這不是內聯的問題)。 static const A b = A();
不允許並且必須是constexpr
或inline
以允許具有類類型的靜態對象的初始化程序,即使類類型的靜態成員不僅僅是聲明。 所以在某些情況下是的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.