为什么可以内联初始化静态常量变量,而不能初始化纯静态(C++)

Why can you initialize a static const variable inline but not a plain static (C++)

本文关键字:初始化 静态 不能 C++ 常量 为什么 变量      更新时间:2023-10-16

如果我要做这个

class Gone
{
    public:
    static const int a = 3;
}

它工作,但如果做

class Gone
{
    public:
    static int a = 3;
}

它给出了一个编译错误。现在我知道为什么第二个不起作用,我只是不知道为什么第一个起作用。

提前谢谢。

此技巧仅适用于常量编译时表达式。考虑以下简单示例:

#include <iostream>
class Foo {
public:
    static const int bar = 0;
};
int main()
{
    std::cout << Foo::bar << endl;
}

它工作得很好,因为编译器知道Foo::bar是0并且永远不会更改。因此,它优化了整个过程。

然而,一旦你取了这样一个变量的地址,整个事情就会崩溃:

int main()
{
    std::cout << Foo::bar << " (" << &Foo::bar << ")" << std::endl;
}

由于编译时常量没有地址,链接器会发送您来修复程序。

现在,示例中的第二种情况不起作用,因为非常量变量不能是常量编译时表达式。因此,您必须在某个地方定义它,并且不能在初始化中分配任何值。

顺便说一下,C++11有constexpr。您可以查看广义常量表达式wiki(或C++11标准:-))以了解更多信息。

此外,要小心——对于某些工具链,当优化关闭时,您将永远无法链接第一个示例中列出的程序,即使您从未获取这些变量的地址。我认为Boost中有一个BOOST_STATIC_CONSTANT宏可以解决这个问题(但不确定它是否有效,因为我认为即使使用该宏,也会看到一些旧gcc的链接失败)。

static const int声明是合法的,因为您声明的是常量,而不是变量。a不是作为变量存在的——编译器可以自由地对其进行优化,在任何出现对Gone::a的引用的地方用声明的值3替换它。C++允许在这种有限的情况下进行静态初始化,因为它是一个整数常量。

您可以在这里找到更多详细信息,包括ISO C++标准引用。

变量的初始化必须在定义的点进行,而不是在一般情况下在声明的点进行。在类括号内,您只有声明,并且您需要在单个翻译单元中提供定义*:

// can be in multiple translation units (i.e. a header included in different .cpp's)
struct test {
   static int x;    // declaration
   static double d; // declaration
};
// in a single translation unit in your program (i.e. a single .cpp file)
int test::x = 5;       // definition, can have initialization
double test::d = 5.0;  // definition

也就是说,static积分常数(并且只有积分常数)有一个例外,您可以在声明中提供常数的值。异常的原因是,它可以用作编译时常量(即定义数组的大小),并且只有当编译器在所有需要的转换单元中看到该常量的值时,这才有可能。

struct test {
   static const int x = 5;  // declaration with initialization
};
const int test::x;          // definition, cannot have initialization

回到最初的问题:

  • 为什么非常量整数不允许使用它
  • 因为初始化发生在定义中,而不是声明
  • 为什么允许使用积分常数
  • 以便它可以在所有翻译单元中用作编译时间常数

*无论何时在程序中使用成员属性,实际规则都需要定义。现在,在C++03中使用的的定义有点棘手,因为它可能并不那么直观,例如,根据标准,将该常数用作右值并不构成使用。在C++11中,为了避免混淆,使用的术语已被odr替换为

类定义中定义了静态常量,因为每个使用代码的人都需要在编译时知道该值,而不是在链接时。一个普通的静态实际上只在类定义中声明,但在一个翻译单元中定义了一次。

我似乎记得最初(ARM)是不允许的,我们曾经使用enum在类声明中定义常量。

显式引入const大小写是为了支持在常量表达式(如数组大小)中使用的标头中的值的可用性。

认为(如果我错了,请评论)严格来说,你仍然需要定义值:

const int Gone::a;

以符合一个定义规则。然而,在实践中,你可能会发现编译器优化了对Gone::a地址的需求,而你却没有它

如果你采取:

const int* b = &Gone::a;

那么你可能会发现你确实需要这个定义。

参见标准,$9.4.2:

iso1998:

"4如果静态数据成员是常量积分或常量枚举类型,其在类定义中的声明可以指定常量初始化器,它应该是一个积分常量表达式(5.19).在这种情况下,成员可以出现在积分常数中表达式。成员仍应在命名空间作用域(如果在程序和命名空间作用域中使用)定义不应包含初始值设定项。"

c++11:草案

"3如果静态数据成员是const有效文本类型,则其类定义中的声明可以指定常量初始值设定项具有初始值设定项子句的大括号或相等初始值设定值积分常数表达式。有效的静态数据成员可以在类定义中用constexpr说明符;如果是,其声明应指明常量初始值设定项大括号或具有initializerclauuse,这是一个积分常量表达式。在两者中在这些情况下,成员可能出现在积分常数表达式中。如果使用了成员,则该成员仍应在命名空间范围中定义在程序中,命名空间范围定义不应包含初始化器。"

我不完全确定这涵盖了什么,但我认为这意味着我们现在可以对浮点和字符串使用相同的习语。