【问题标题】:Why can't I have a non-integral static const member in a class?为什么我不能在一个类中有一个非整数静态 const 成员?
【发布时间】:2010-09-27 02:00:51
【问题描述】:

我注意到 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)。

编辑2:

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

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

【问题讨论】:

  • 什么编译器/平台,或者你看到它的倍数?
  • 您在 VS7.1 中收到什么错误消息?

标签: c++


【解决方案1】:

问题在于,对于整数,编译器通常不必为常量创建内存地址。它在运行时不存在,每次使用它都会内联到周围的代码中。它仍然可以决定给它一个内存位置 - 如果它的地址曾经被占用(或者如果它通过 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

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

【讨论】:

  • 静态 const double d = 1.0;不是有效的 C++。它根本不应该编译。这种形式只允许用于整数类型。这就是 numeric_limits 中的 min 和 max 是函数而不是静态常量的原因。我不知道为什么它会为你编译。
  • 这似乎是一个 gcc 扩展。使用 -pedantic 编译产生:“foo.cpp:4:错误:ISO C++ 禁止初始化非整数类型 'const double' 的成员常量 'd'”
  • @JohannesSchaub-litb :感谢您的调查。这个答案应该在顶部进行编辑以表明这一点。
  • "是什么意思,问题是对于整数,编译器通常不必为常量创建内存地址。它在运行时不存在,每次使用其中的一部分被内联到周围的代码中。” ?你能更清楚地说明这一点吗?
  • the compiler usually doesn't have to ever create a memory address for the constant:作为一个思想实验,获取常量的地址并将其存储在变量中会强制编译器为常量创建存储空间吗?
【解决方案2】:

我认为没有技术原因

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

【讨论】:

  • 我不明白“允许浮点模板参数会带来自己的一套规则,不值得麻烦。”。也难以获得“如果在翻译环境中计算浮动表达式,则算术精度和范围应至少与在执行环境中计算表达式一样大。”
  • @Chubsdad 前者是因为浮点计算通常不准确,这可能因实现而异。虽然1+1 在任何实现中始终为2,但类似的简单计算可以在不同的浮点模型上使用浮点数学产生不同的结果(请注意,我不喜欢这些问题,但我知道它们存在)。
  • 对于后一个问题,我不太清楚其中的原因。请注意,编译环境可能与交叉编译器的执行环境不同。这里的基本原理可能是通过在编译时计算结果来确保不会使高精度变得更糟。但也许你可以提出一个单独的 SO 问题。
  • 不仅如此,一个编译后的代码(例如armv5)可以在多个CPU上运行。例如。一个 ArmV7 处理器。这可以实现不同的(中间)浮动精度。 PowerPC 是一个与 Intel 行为不同的 CPU 示例。一些旧的英特尔(P6 时代)有一些错误......即使只有一个二进制文件,执行也可能不同。标准委员会担心这罐蠕虫,并决定排除static floats,这对我来说是有意义的。
  • Note DR 1826 解释了为什么他们对 C++11 保留此限制。
【解决方案3】:

它并没有真正给出理由,但这是 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) 和 consts 初始化为 常量表达式。在模板中,整数模板 也可以使用参数 (C.13.3)。浮动文字 (4.5.1) 仅在显式转换为整数时才能使用 类型。函数、类对象、指针和引用 可以用作 sizeof 的操作数 仅限运算符 (6.2)。

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

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

【讨论】:

  • c++1x 将修复该错误:(最近的草案):“使用名称显示为潜在评估表达式的对象或非重载函数,除非它是满足以下要求的对象出现在常量表达式中”。只有在使用时才必须定义静态,所以这解决了它
  • 在当今的大多数实现工作中没有提供任何定义,原因是:“如果程序包含违反不需要诊断的规则,则本国际标准对实现没有要求程序。”
  • 声明您需要提供定义的规则被标记为“不需要诊断”。因此,大多数编译(包括 comeau)都可以正常运行,除非您获取对象的地址并且对象未在类定义中初始化,此时将导致链接器错误。
  • 好的。今天我发现c++03已经改变了规则,如果变量出现在需要整数常量表达式的地方,则允许省略定义。很高兴知道:)
  • @litb:你确定吗? - C++03 9.4.2 第 4 段似乎仍然明确指出必须存在一个定义(如果使用该成员)。
【解决方案4】:

我不知道为什么它会处理与 int 不同的 double。我以为我以前用过那个表格。这是另一种解决方法:

class Now_Better
{
    static double const d;
};

在您的 .cpp 文件中:

double const Now_Better::d = 1.0;

【讨论】:

  • 是的,我避开这个可能的解决方案只是因为我认为我的解决方案更容易阅读。我不喜欢 have 在单独的文件中声明和初始化一个值(我的类在 .h 中)。谢谢。
【解决方案5】:

这是我基于 Stroustrup 关于课堂定义的陈述的理解

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

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

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

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

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

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

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

【讨论】:

    猜你喜欢
    • 2012-01-21
    • 1970-01-01
    • 2012-04-04
    • 1970-01-01
    • 2012-03-28
    • 2023-03-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多