C ++积分常数+选择运算符=问题!

时间:2010-11-17 21:41:14

标签: c++ portability

我最近在一些正在开发的大型程序中发现了一个烦人的问题;我想了解如何以最佳方式解决它。我将代码剪切到以下最小的示例。

#include <iostream>
using std::cin;
using std::cout;

class MagicNumbers
{
public:
  static const int BIG = 100;
  static const int SMALL = 10;
};

int main()
{
  int choice;
  cout << "How much stuff do you want?\n";
  cin >> choice;
  int stuff = (choice < 20) ? MagicNumbers::SMALL : MagicNumbers::BIG; // PROBLEM!
  cout << "You got " << stuff << "\n";
  return 0;
}

在使用-O0或-O1进行编译时,我在gcc 4.1.2中遇到链接错误,但在使用-O2或-O3进行编译时一切正常。无论优化选项如何,它都可以使用MS Visual Studio 2005很好地链接。

  

test.cpp :(。text + 0xab):未定义的引用`MagicNumbers :: SMALL'

     

test.cpp :(。text + 0xb3):未定义引用`MagicNumbers :: BIG'

我查看了中间汇编代码,是的,非优化代码将SMALL和BIG视为外部int变量,而优化后的代码使用实际数字。以下每项更改都可以解决问题:

  • 对常量使用enum而不是int:enum {SMALL = 10}

  • 在每次使用时投放常量(任何一个):(int)MagicNumbers::SMALL(int)MagicNumbers::BIG或甚至MagicNumbers::SMALL + 0

  • 使用宏:#define SMALL 10

  • 不使用选择运算符:if (choice < 20) stuff = MagicNumbers::SMALL; else stuff = MagicNumbers::BIG;

我最喜欢第一个选项(但是,它并不理想,因为我们实际上对这些常量使用uint32_t而不是int,而enum与int同义)。但我真正想问的是:它的错误是什么?

我不应该理解不了解静态积分常数是如何工作的吗?

我应该责怪gcc并希望修复(或者最新版本已经有修复,或者可能有一个模糊的命令行参数使这项工作)?

与此同时,我只是使用优化编译我的代码,调试很痛苦:-O3

8 个答案:

答案 0 :(得分:20)

This is a known issue。标准是责备或你没有提供静力学的定义。根据您的观点:)

答案 1 :(得分:7)

C ++中的静态数据成员don't work like that

  

静态数据成员不属于   给定类类型的对象;他们   是单独的对象。结果,   静态数据成员的声明是   不被视为定义。数据   成员在类范围内声明,但是   定义在文件范围内执行。   这些静态成员有外部   键。

你只是声明那些常量,即使你正在初始化它们。您仍然必须在命名空间范围定义它们:

class MagicNumbers
{
public:
    static const int BIG = 100;
    static const int SMALL = 10;
};

const int MagicNumbers::BIG;
const int MagicNumbers::SMALL;

这将消除链接错误。

答案 2 :(得分:7)

尽管有传统的建议,我发现static const int ...总是给我带来比好老enum { BIG = 100, SMALL = 10 };更令人头疼的问题。在C ++ 11提供强类型枚举的情况下,我现在使用static const int ...的原因更少了。

答案 3 :(得分:3)

嘿,根据C ++标准,9.4.2(class.static.data):

  

如果静态数据成员是const   字面类型,它的声明在   类定义可以指定一个   支撑或等于初始化器   每个初始化子句都是一个   assignment-expression是一个常量   表达。静态数据成员   文字类型可以在中声明   使用constexpr进行类定义   符;如果是这样,它的声明   应指明一个   支撑或等于初始化器   每个初始化子句都是一个   assignment-expression是一个常量   表达。 [注意:在这两个方面   案件,会员可能会出现在   常数表达式。 - 结束注释]   会员仍应在a。中定义   命名空间作用域,如果它在   程序和命名空间范围   定义不得包含   初始化程序。

所以声明是正确的,但你仍然需要在某个地方有一个定义。我一直认为你可以掌握这个定义,但我认为这不符合标准。

答案 4 :(得分:1)

我很难断言这是任何人的错误。

在声明点给定值的静态const积分不是变量,它们是常量表达式。对于有变量,您仍然需要定义它。

三元运算符的规则非常复杂,可能必然如此,实际上并没有真正说明常量表达式,只有rvalues;很明显,编译器认为它们应该是变量,除非优化得到提升。我认为可以自由地解释表达式(作为常量表达式或变量)。

答案 5 :(得分:1)

我是C ++的新手,但我认为你的类声明只声明那些静态成员存在,你仍然需要在某处定义它们:

class MagicNumbers
{
public:
  static const int BIG;
  static const int SMALL;
};

const int MagicNumbers::BIG = 100;
const int MagicNumbers::SMALL = 10;

更高的优化级别可能包括足够的静态分析级别,以确定BIGSMALL可以与其实际值交换,而不是为它们提供任何实际存储(语义将是因此,在这种情况下定义这些变量将是多余的,因此它链接正常。

答案 6 :(得分:0)

你仍然需要在某处为他们分配空间:

class MagicNumbers
{
public:
  static const int BIG = 100;
  static const int SMALL = 10;
};
const int MagicNumbers::BIG;
const int MagicNumbers::SMALL;

答案 7 :(得分:0)

为什么你的神奇数字在课堂上?

namespace MagicNumbers {
    const int BIG = 100;
    const int SMALL = 10;
}

问题解决了,无需担心C ++标准中的缺陷。