为什么我不能在一个类中有一个非整数静态 const 成员?

我注意到 C + + 不会编译以下代码:

class No_Good {
static double const d = 1.0;
};

然而,它将很高兴地允许一种变体,其中 double 被更改为 int、 unsigned 或任何整数类型:

class Happy_Times {
static unsigned const u = 1;
};

我的解决办法是把它改成:

class Now_Good {
static double d() { return 1.0; }
};

并认为编译器会聪明到在必要的地方内联,但这让我很好奇。

为什么 C + + 设计器允许我使用 int 或 unsigned 来表示静态常量,而不允许使用 double 呢?

编辑: 我在 Windows XP 上使用 Visual Studio 7.1(. net 2003)。

编辑2:

问题已经得到解答,但为了完成,我看到的错误是:

error C2864: 'd' : only const static integral data members can be initialized inside a class or struct
44226 次浏览

我不知道为什么它会把一个 double 和一个 int 区分开来。我以为我以前用过这个表格。这里有一个替代的解决办法:

class Now_Better
{
static double const d;
};

在你的.cpp 文件中:

double const Now_Better::d = 1.0;

问题是,对于整数,编译器 通常不必为常量创建内存地址。它在运行时不存在,并且它的每次使用都内联到周围的代码中。它仍然可以决定给它一个内存位置-如果它的地址曾经被获取(或者如果它通过常量引用传递给一个函数) ,那么它必须。为了给它一个地址,它需要在某个翻译单元中定义。在这种情况下,您需要将声明与定义分开,否则它将在多个翻译单元中定义。

使用不带优化(-O0)的 g + + ,它自动内联常数整数变量,但不内联常数双精度值。在更高的优化级别(例如 -O1) ,它内联常数双精度。因此,以下代码在 -O1编译,而不是在 -O0编译:

// File a.h
class X
{
public:
static const double d = 1.0;
};


void foo(void);


// File a.cc
#include <stdio.h>


#include "a.h"


int main(void)
{
foo();
printf("%g\n", X::d);


return 0;
}


// File b.cc
#include <stdio.h>


#include "a.h"


void foo(void)
{
printf("foo: %g\n", X::d);
}

命令行:

g++ a.cc b.cc -O0 -o a   # Linker error: ld: undefined symbols: X::d
g++ a.cc b.cc -O1 -o a   # Succeeds

为了实现最大的可移植性,应该在头文件中声明常量,并在某些源文件中定义一次。如果没有优化,这不会影响性能,因为你不是在优化,但是如果启用了优化,这可能会影响性能,因为编译器不能再将这些常量内联到其他源文件中,除非你启用“整个程序优化”。

我看不出有什么技术原因

struct type {
static const double value = 3.14;
};

是被禁止的。任何您发现它工作的场合都是由于不可移植的实现定义的特性。它们的作用似乎也很有限。对于在类定义中初始化的整数常量,您可以使用它们并将它们作为非类型参数传递给模板,并将它们用作数组维度的大小。但是对于浮点常量不能这样做。允许浮点模板参数会带来一系列自己的规则,这些规则并不真正值得麻烦。

尽管如此,下一个 C + + 版本将允许使用 constexpr:

struct type {
static constexpr double value = 3.14;
static constexpr double value_as_function() { return 3.14; }
};

使 type::value成为一个常量表达式。与此同时,最好的办法是遵循 std::numeric_limits也使用的模式:

struct type {
static double value() { return 3.14; }
};

它不会返回一个常量表达式(编译时不知道值) ,但这只是理论上的,因为实际上该值无论如何都是内联的。参见 Constexpr提案。里面有

4.4

Floating-point constant expressions

传统上,评价 浮点常数表达式 编译时是一个棘手的问题 我们建议统一性和普遍性 允许常量表达式数据 初始化的浮点类型 任何浮点常数 表达式。这也将增加 与 C99[ ISO99,6.6]的兼容性 这就允许

[ # 5]计算结果为 常数需要在几个 如果浮动表达式是 在翻译环境中进行评估 环境,算术精度和 范围至少须与 表达式在 执行环境。

这并没有给出一个合理的解释,但是以下是 Stroustrup 在“ C++程式语言第三版”中对此的解释:

10.4.6.2成员常数

也可以初始化 静态积分常数构件 增加一个 常数表达式常数表达式 初始值设定项转换为其成员声明。 例如:

class Curious {
static const int c1 = 7;        // ok, but remember definition
static int c2 = 11;             // error: not const
const int c3 = 13;              // error: not static
static const int c4 = f(17);    // error: in-class initializer not constant
static const float c5 = 7.0;    // error: in-class not integral
// ...
};

但是,初始化的成员仍然必须(唯一地)定义 某个地方,而初始化程序可能不会 重复:

const int Curious::c1;  // necessary, but don't repeat initializer here

我认为这是一个错误的功能。当你需要一个符号常数 在类声明中,使用 枚举数(4.8,14.4.6,15.3) 例如:

class X {
enum { c1 = 7, c2 = 11, c3 = 13, c4 = 17 };
// ...
};

这样,其他地方就不需要成员定义,而您也不需要 试图声明变量, 浮点数等。

在 C.5节(常量表达式)的附录 C (技术)中,Stroustrup 谈到了“常量表达式”:

在数组边界(5.2)、大小写标签(6.3.2)等位置, 和枚举数的初始值设定项(4.8) ,C + + 需要一个 常量表达式 。常量表达式的计算结果为 整数或枚举常数。这样的表达式 是由文字(4.3.1,4.4.1,4.5.1) , 枚举数(4.8)和由 在模板中,一个整数模板 参数也可以使用(C. 13.3)。浮点数字(4.5.1) 只有在显式转换为积分时才能使用 函数、类对象、指针和引用 可以用作 大小的操作数 营办商(6.2)。

直观地说,常量表达式是简单的表达式 可由编译器在程序之前计算的 链接(9.1)并开始运行。

请注意,他几乎没有提到浮点数,因为它能够在“常量表达式”中进行游戏。我怀疑这些类型的常量表达式之所以没有使用浮点数,仅仅是因为它们不够“简单”。

以下是我对 Stroustrup 关于课堂定义的陈述的理解

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

Http://www.stroustrup.com/bs_faq2.html#in-class

所以基本上,这是不允许的,因为 C + + 不允许。为了使链接器规则更加简单,C + + 要求每个对象都有唯一的定义。

Static member 在类作用域中只有一个实例,不像 C 语言中大量使用的常规静态变量,C 语言在一个转换单元中只有一个实例。

如果静态成员是在类中定义的,并且类定义将被包含到许多翻译单元中,那么链接器必须做更多的工作来决定哪个静态成员应该作为所有相关翻译单元中的唯一成员使用。

但对于规则静态变量,它们只能在一个翻译单元内使用,即使在同名的不同翻译单元内使用不同的静态变量,它们也不会相互影响。链接器可以做一些简单的工作来链接一个转换单元内的常规静态变量。

为了减少复杂度并给出基函数,C + + 为整型或枚举类型的静态常量提供了唯一的类内定义。