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

时间:2012-03-11 17:01:19

标签: c++ static const

为什么我不能在类中初始化非const 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数组?

5 个答案:

答案 0 :(得分:122)

为什么我无法在课程中初始化static数据成员?

C ++标准只允许在类中初始化静态常量积分或枚举类型。这是a被允许初始化而其他人不被初始化的原因。

参考:
C ++ 03 9.4.2静态数据成员
§4

  

如果静态数据成员是const integer或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恰当地解释了这个 here

  

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

为什么只有static const整数类型&枚举允许进行类内初始化?

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

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

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

枚举是允许的,因为枚举类型的值可以在需要整数的地方使用。见上面的引文


这在C ++ 11中有何变化?

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

C ++ 11 9.4.2静态数据成员
§3

  

如果静态数据成员是const文字类型,则其在类定义中的声明可以指定大括号或等于初始化,其中每个 initializer-clause 这是一个赋值表达式是一个常量表达式。文本类型的静态数据成员可以在类定义中使用constexpr specifier;声明,如果是这样,其声明应指定大括号或等于初始化,其中每个初始化程序-clause 赋值表达式是一个常量表达式。 [注意:在这两种情况下,成员可能会出现在常量表达式中。 -end note]如果在程序中使用该成员,并且命名空间作用域定义不包含初始化程序,则该成员仍应在命名空间作用域中定义。

此外,C ++ 11 允许(§12.6.2.8)非静态数据成员在其声明的位置(在其类中)进行初始化。这将意味着很容易的用户语义。

请注意,这些功能尚未在最新的gcc 4.7中实现,因此您可能仍会遇到编译错误。

答案 1 :(得分:3)

这似乎是过去简单连接器的遗留物。您可以在静态方法中使用静态变量作为解决方法:

// 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

答案 2 :(得分:1)

我认为可以防止你混淆声明和定义。 (想想如果将文件包含在多个位置可能会出现的问题。)

答案 3 :(得分:0)

这是因为所有翻译单元只能使用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];是一个重新定义,而static int i[]; int A::i[5];不是,但与2个外部代码不同,如果您在类中执行static int i[]; static int i[5];,则编译器仍会检测到重复成员。

答案 4 :(得分:-3)

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