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

Yis*_*ang 93 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;
}
Run Code Online (Sandbox Code Playgroud)

编译器发出以下错误:

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]’
Run Code Online (Sandbox Code Playgroud)

我有两个问题:

  1. 为什么我不能static在课堂上初始化数据成员?
  2. 为什么我不能static在类中初始化数组,甚至是const数组?

Alo*_*ave 127

为什么我不能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 };

};
Run Code Online (Sandbox Code Playgroud)

为什么标准不允许这样做?

Bjarne 在这里恰当地解释了这一点:

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

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

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

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

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

允许枚举这是因为枚举类型的值可以在期望int的位置使用.看上面的引文


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

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

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

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

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

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

  • c ++ 11中的情况有所不同.答案可以使用更新. (7认同)
  • 这似乎不是真的:*"请注意,只有静态const整数可以被视为编译时常量.编译器知道整数值不会随时改变,因此它可以应用自己的魔法并应用优化,编译器简单地内联这样的类成员,即**它们不再存储在内存中**,"*你确定它们*必然*没有存储在内存中吗?如果我为会员提供定义怎么办?`&member`将返回什么? (4认同)
  • 您提到的[解决方法] [不起作用](http://ideone.com/GihZ4)与g ++. (4认同)
  • @Nawaz:因为C++ 03只允许*constant-initializer*用于static和const integral和const枚举类型而没有其他类型,所以C++ 11将它扩展为*const文字类型*,它放宽了In-Class的规范初始化.C++ 03中的限制可能是一种疏忽,可以保证更改,因此在C++ 11中得到纠正,如果有任何传统的战术原因我不了解它们.如果你知道任何随时分享他们. (3认同)
  • @Als:是的.这就是我的问题.那么为什么C++只允许对整数类型进行类内初始化,你的答案没有正确回答.想想为什么它不允许初始化`static const char*`成员? (2认同)
  • 所以只是为了确认...我们所说的是:(1)就是这样,因为C++ 03语言标准就是这样说的; (2)没有潜在的逻辑理由应该是这样的; (3)它在C++ 11中修复. (2认同)

not*_*ser 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();
Run Code Online (Sandbox Code Playgroud)

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

int compilation_unit_a() {  
    return Class::replacement_for_initialized_static_non_const_variable()[1]++;
}
Run Code Online (Sandbox Code Playgroud)

// 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;
}
Run Code Online (Sandbox Code Playgroud)

建造:

g++ -std=gnu++0x -save-temps=obj -c compilation_unit_a.cxx 
g++ -std=gnu++0x -o main main.cxx compilation_unit_a.o
Run Code Online (Sandbox Code Playgroud)

跑:

./main
Run Code Online (Sandbox Code Playgroud)

这有效的事实(始终如一,即使类定义包含在不同的编译单元中)表明今天的链接器(gcc 4.9.2)实际上已经足够聪明了。

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


Lew*_*sey 5

A::a这是因为所有翻译单元只能使用一种定义。

如果您static int a = 3;在所有翻译单元中包含的标头中的类中执行,那么您将获得多个定义。因此,静态的非外线定义会强制产生编译器错误。

使用static inlinestatic const补救这个问题。static inline仅在翻译单元中使用该符号时才具体化该符号,并确保链接器仅选择并保留一份副本(如果该符号由于位于 comdat 组中而在多个翻译单元中定义)。constat 文件范围使编译器永远不会发出符号,因为它总是在代码中立即替换,除非extern使用 ,这在类中是不允许的。

需要注意的一件事是static inline int b;被视为定义,而static const int borstatic const A b;仍被视为声明,并且如果不在类内定义它,则必须在行外定义它。有趣的static constexpr A b;是,它被视为定义,而 isstatic 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;
}
Run Code Online (Sandbox Code Playgroud)

静态成员是一个彻底的文件范围声明extern int A::a;(只能在类中进行,并且外部定义必须引用类中的静态成员,并且必须是定义并且不能包含 extern),而非静态成员是类的完整类型定义,与不带extern. 它们是隐式定义。int i[]; int i[5];重新定义也是如此,而不是,但与 2 个外部变量不同,如果您在类中static int i[]; int A::i[5];这样做,编译器仍然会检测到重复的成员。static int i[]; static int i[5];