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


116

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

1
我认为主要原因是正确无误。原则上,您可能会做您正在说的事情,但是会有一些怪异的副作用。就像是否允许使用数组示例一样,您可能可以获取A :: c [0]的值,但无法将A :: c传递给函数,因为这将需要一个地址和编译时常量没有地址。C ++ 11通过使用constexpr启用了其中的一些功能。
沃恩·卡托

伟大的问题和错误的答案。对我有帮助的链接:msdn.microsoft.com/en-us/library/0e5kx78b.aspx
ETFovac 2014年

Answers:


144

为什么我不能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文字类型,则其在类定义中的声明可以指定大括号或相等初始化器,其中每个作为赋值表达式的初始化子句都是一个常量表达式。可以在类定义中使用if 声明静态类型的静态数据成员,如果声明,则其声明应指定大括号或等于初始化器,其中每个作为赋值表达式的初始化器子句constexpr specifier;是一个常量表达式。[注意:在这两种情况下,该成员都可能出现在常量表达式中。— —尾注]如果在程序中使用了该成员,则该成员仍应在命名空间范围中定义,并且该命名空间范围定义不应包含初始化程序。

同样,C ++ 11 允许(第12.6.2.8节)在其声明的位置(在其类中)初始化非静态数据成员。这将意味着简单的用户语义。

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


7
c ++ 11中的情况有所不同。答案可以使用更新。
bames53

4
似乎并非如此:“请注意,只有静态const整数可以被视为编译时间常数。编译器知道该整数值随时都不会改变,因此它可以运用自己的魔力并进行优化,编译器只是简单地内联这样的类成员,即它们不再存储在内存中,” 您确定它们不一定存储在内存中吗?如果我为成员提供定义怎么办?什么会&member回来?
纳瓦兹2012年

2
@Als:是的。那就是我的问题。因此,为什么C ++只允许对整数类型进行类内初始化,但您的答案未正确回答。想一想为什么不允许static const char*成员初始化?
纳瓦兹2012年

3
@Nawaz:因为C ++ 03只允许使用static - const整数和const枚举类型的常量初始化器,而没有其他类型,所以C ++ 11将此常量扩展为const文字类型,从而放宽了In-Class初始化的规范。如果有任何传统的战术上的原因使我不知道,则C ++ 03中的疏忽可能需要进行更改,因此在C ++ 11中已得到纠正。他们。
Alok Save 2012年

4
您提到的“替代方法”标准不适用于g ++。
iammilind

4

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

// 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在手臂和3210x86上打印。


1

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


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。的文件范围声明具有相同的规则。它们是隐式定义。因此int i[]; int i[5];是一个重新定义,static int i[]; int A::i[5];但不是,但是不像2个externs,如果您static int i[]; static int i[5];在类中进行编译,则编译器仍会检测到重复成员。


-3

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.