C++ integral constants + choice operator = problem!

倖福魔咒の 提交于 2019-11-29 19:16:43

问题


I have recently discovered an annoying problem in some large program i am developing; i would like to understand how to fix it in a best way. I cut the code down to the following minimal example.

#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;
}

I get link errors in gcc 4.1.2 when compiling with -O0 or -O1 but everything is OK when compiling with -O2 or -O3. It links well using MS Visual Studio 2005 regardless of optimization options.

test.cpp:(.text+0xab): undefined reference to `MagicNumbers::SMALL'

test.cpp:(.text+0xb3): undefined reference to `MagicNumbers::BIG'

I looked at the intermediate assembly code, and yes, the non-optimized code regarded SMALL and BIG as external int variables, while the optimized one used the actual numbers. Each of the following changes fixes the problem:

  • Use enum instead of int for constants: enum {SMALL = 10}

  • Cast the constant (any one) at each usage: (int)MagicNumbers::SMALL or (int)MagicNumbers::BIG or even MagicNumbers::SMALL + 0

  • Use a macro: #define SMALL 10

  • Not use the choice operator: if (choice < 20) stuff = MagicNumbers::SMALL; else stuff = MagicNumbers::BIG;

I like the first option best (however, it's not ideal because we actually use uint32_t instead of int for these constants, and enum is synonymous with int). But what i really want to ask is: whose bug is it?

Am i the one to blame for not understanding how static integral constants work?

Should i blame gcc and hope for a fix (or maybe the latest version already has a fix, or maybe there is an obscure command-line argument to make this work)?

Meanwhile, i just compile my code with optimizations, and it's a pain to debug :-O3


回答1:


In spite of the conventional advice, I have found that static const int ... invariably gives me more headaches than good old enum { BIG = 100, SMALL = 10 };. And with C++11 providing strongly-typed enums, I now have even less cause to use static const int ....




回答2:


This is a known issue. The Standard is to blame or you for not providing a definition of the statics. Depending on your point of view :)




回答3:


Static data members don't work like that in C++:

Static data members are not part of objects of a given class type; they are separate objects. As a result, the declaration of a static data member is not considered a definition. The data member is declared in class scope, but definition is performed at file scope. These static members have external linkage.

You're only declaring those constants, even though you're initializing them. You still have to define them at namespace scope:

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

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

That will get rid of the link errors.




回答4:


Heh, according to the C++ standard, 9.4.2 (class.static.data):

If a static data member is of const literal type, its declaration in the class definition can specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression. A static data member of literal type can be declared in the class definition with the constexpr specifier; if so, its declaration shall specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression. [ Note: In both these cases, the member may appear in constant expressions. —end note ] The member shall still be defined in a namespace scope if it is used in the program and the namespace scope definition shall not contain an initializer.

So the declaration is correct, but you still need to have a definition somewhere. I always thought you could skill the definition, but I suppose that isn't standard conforming.




回答5:


I'm new to C++, but I think that your class declaration only declares that those static members exist, you still need to define them somewhere:

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

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

The higher optimisation levels probably include a level of static analysis thorough enough to determine that BIG and SMALL can be exchanged with their actual values and not to give them any actual storage (the semantics will be the same), so defining these variables in this circumstance would be redundant, hence it links OK.




回答6:


I'd be hard pressed to assert that it's anyone's bug.

Static const integrals given values at point of declaration are not variables, they're constant expressions. For there to be a variable you still need to define it.

The rules wrt the ternary operator are pretty absurdly complex, probably necessarily so, and actually doesn't really say anything about constant expressions, only rvalues; obviously the compiler thinks they should be variables unless optimization is cranked way up. I think it's free to interpret the expression either way (as a constant expression or as variable).




回答7:


You still need to allocate space for them somewhere:

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



回答8:


Why are your magic numbers in a class?

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

Problem solved without needing to worry about flaws in the C++ standard.



来源:https://stackoverflow.com/questions/4209433/c-integral-constants-choice-operator-problem

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!