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

时间:2008-12-16 01:53:06

标签: c++

我注意到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 ++设计器允许我使用静态const一个int或unsigned,而不是一个double?

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

EDIT2:

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

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

5 个答案:

答案 0 :(得分:48)

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

使用没有优化的g ++(-O0),它会自动内联常量整数变量但不是常量的双精度值。在更高的优化级别(例如-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

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

答案 1 :(得分:18)

我认为没有技术原因

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]一个计算结果为a的表达式   几个要求常数   上下文。如果浮动表达是   在翻译环境中进行评估   ronment,算术精度和   范围至少应该如此大   表达式正在评估中   执行环境。

答案 2 :(得分:7)

它并没有真正给出理由,但这就是Stroustrup在“C ++编程语言第三版”中对此有何看法:

  

10.4.6.2成员常量

     

也可以初始化a   静态积分常数成员   添加常量表达式   初始化程序到其成员声明。   例如:

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)和 consts 初始化   常数表达式。在模板中,是一个整数模板   也可以使用参数(C.13.3)。浮动文字(4.5.1)   只有在显式转换为整数时才能使用   类型。函数,类对象,指针和引用   可以用作 sizeof 的操作数   仅限运营商(6.2)。

     

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

请注意,他几乎没有留下浮动点,因为它能够在“常量表达式”中播放。我怀疑浮点数被排除在这些类型的常量表达式之外,仅仅因为它们不够“简单”。

答案 3 :(得分:4)

我不知道为什么它会处理与int不同的双重差异。我以为我以前用过这种形式。这是另一种解决方法:

class Now_Better
{
    static double const d;
};

在你的.cpp文件中:

double const Now_Better::d = 1.0;

答案 4 :(得分:-1)

这是基于Stroustrup关于课堂内定义的陈述

的理解
  

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

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

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

静态成员在类范围中只有一个实例,而不像在C中大量使用的常规静态变量,它在一个转换单元中只有一个实例。

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

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

为了减少复杂性并提供基本函数,C ++为整数或枚举类型的静态const提供了唯一的类内定义。