为什么我不能在类中初始化非常数静态成员或静态数组?

为什么不能在类中初始化非常数 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数组?
140797 次浏览

为什么我不能在类中初始化 static数据成员?

C + + 标准只允许在类内初始化静态常量整数或枚举类型。这就是为什么 a允许被初始化,而其他的则不允许。

参考文献:
C + + 039.4.2静态数据成员
4

如果一个静态数据成员是 const 整数或 const 枚举类型,那么它在类定义中的声明可以指定一个常数初始化器,该初始化器应该是一个整数常数表达式(5.19)。在这种情况下,成员可以以整数常数表达式出现。如果成员在程序中使用,并且命名空间范围定义不包含初始值设定项,则该成员仍应在命名空间范围中定义。

什么是积分类型?

C + + 033.9.1基本类型
7

类型 bool、 char、 wchar _ t 以及有符号和无符号整数类型统称为整数类型。43)整数类型的同义词是整数类型。

脚注:

43) 因此,枚举(7.2)不是整数; 然而,枚举可以提升为 int、 unsignedint、 long 或 unsignedlong,如4.5中指定的。

解决办法:

可以使用 Enum 诡计在类定义中初始化数组。

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


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


};

为什么标准不允许这样?

比昂恰如其分地解释了这一点。 翻译: 奇芳,校对: 奇芳,校对: 奇芳,奇芳,奇芳,奇芳,奇芳,奇芳,奇芳,奇芳,奇芳,奇芳,奇芳,奇芳,奇芳,奇芳,奇芳,奇芳,奇芳,奇芳,奇芳:

类通常在头文件中声明,而头文件通常包含在许多翻译单元中。但是,为了避免复杂的链接器规则,C + + 要求每个对象都有唯一的定义。如果 C + + 允许在类中定义需要作为对象存储在内存中的实体,那么这个规则就会被打破。

为什么只允许 static const整数类型和枚举类内初始化?

答案就隐藏在比昂的名言中仔细阅读,
”C + + 要求每个对象都有唯一的定义。如果 C + + 允许在类中定义需要作为对象存储在内存中的实体,那么这一规则就会被打破。”

请注意,只有 static const整数可以被视为编译时常数。编译器知道整数值不会随时改变,因此它可以应用自己的魔术和应用优化,编译器只是内联这样的类成员,即它们不再存储在内存中,因为需要存储在内存中被删除,它给这些变量的规则的例外 Bjarne 提到。

这里值得注意的是,即使 static const整数值可以进行类内初始化,也不允许对这些变量进行寻址。如果(并且仅当)静态成员具有类外定义,则可以获取该成员的地址。这进一步验证了上述推理。

枚举是允许的,因为枚举类型的值可以在需要 int 的地方使用


这在 C + + 11中是如何改变的呢?

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

C + + 119.4.2静态数据成员
3

如果一个静态数据成员是 const 文字类型的,那么它在类定义中的声明可以指定一个 大括号或等于初始值设定项,其中每个 初始化子句都是一个常量表达式。一个文字类型的静态数据成员可以用 constexpr specifier;在类定义中声明,如果是这样的话,它的声明应该指定一个 大括号或等于初始值设定项,其中每个 初始化子句都是一个常量表达式。[注意: 在这两种情况下,成员可能以常数表达式出现。如果成员在程序中使用,并且命名空间范围定义不包含初始值设定项,那么它仍然应该在命名空间范围中定义。

另外,C + + 11 威尔允许(12.6.2.8)在声明它的地方(在它的类中)初始化非静态数据成员。这意味着用户语义非常简单。

注意,这些特性还没有在最新的 gcc 4.7中实现,因此您可能仍然会遇到编译错误。

我认为这是为了防止混淆声明和定义。(考虑一下如果在多个位置包含该文件可能会出现的问题。)

静态变量是特定于类的。构造函数初始化实例的属性 ESPECIALY。

这似乎是简单链接器旧时代的遗留物。您可以在静态方法中使用静态变量作为解决方案:

// 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;和显式初始化 static const int b0是不一样的(前者可以是一个声明,但是如果只允许该类型的声明,那么后者就是一个错误。后者只能用于定义。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的文件范围声明相同的规则。它们是隐式定义。因此,int i[]; int i[5];是一个重新定义,而 static int i[]; int A::i[5];不是,但不同于2个外部,编译器仍然会检测到一个重复的成员,如果你在类中执行 static int i[]; static int i[5];