Интегральные константы C++ + оператор выбора = проблема!
Недавно я обнаружил раздражающую проблему в какой-то большой программе, которую я разрабатываю; Я хотел бы понять, как это исправить наилучшим образом. Я сократил код до следующего минимального примера.
#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;
}
Я получаю ошибки ссылки в gcc 4.1.2 при компиляции с -O0 или -O1, но все нормально при компиляции с -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 ответов
Несмотря на общепринятый совет, я обнаружил, что static const int ...
неизменно дает мне больше головной боли, чем старый добрый enum { BIG = 100, SMALL = 10 };
, И с C++11, обеспечивающим строго типизированные перечисления, у меня теперь есть еще меньше причин использовать static const int ...
,
Это известная проблема. Стандарт виноват или вы не предоставили определение статики. В зависимости от вашей точки зрения:)
Статические члены-данные не работают так в C++:
Статические члены-данные не являются частью объектов данного типа класса; они являются отдельными объектами. В результате объявление статического члена данных не считается определением. Член данных объявляется в области видимости класса, но определение выполняется в области видимости файла. Эти статические члены имеют внешнюю связь.
Вы только объявляете эти константы, даже если вы их инициализируете. Вы все еще должны определить их в области имен:
class MagicNumbers
{
public:
static const int BIG = 100;
static const int SMALL = 10;
};
const int MagicNumbers::BIG;
const int MagicNumbers::SMALL;
Это избавит от ошибок ссылок.
Хех, в соответствии со стандартом C++, 9.4.2 (class.static.data):
Если член статических данных имеет константный литеральный тип, его объявление в определении класса может указывать инициализатор скобок или равных, в котором каждое предложение инициализатора, являющееся выражением присваивания, является константным выражением. Статический член данных литерального типа может быть объявлен в определении класса с помощью спецификатора constexpr; если это так, его объявление должно указывать инициализатор скобок или равных, в котором каждое предложение инициализатора, являющееся выражением присваивания, является константным выражением. [Примечание: в обоих этих случаях член может появляться в константных выражениях. - примечание конца] Элемент все еще должен быть определен в области пространства имен, если он используется в программе, и определение области пространства имен не должно содержать инициализатор.
Таким образом, декларация верна, но вам все равно нужно где-то определить. Я всегда думал, что ты умеешь определять это определение, но я полагаю, что это не соответствует стандарту.
Я новичок в C++, но я думаю, что ваше объявление класса только объявляет, что эти статические члены существуют, вам все равно нужно их где-то определить:
class MagicNumbers
{
public:
static const int BIG;
static const int SMALL;
};
const int MagicNumbers::BIG = 100;
const int MagicNumbers::SMALL = 10;
Более высокие уровни оптимизации, вероятно, включают уровень статического анализа, достаточно тщательный, чтобы определить, что BIG
а также SMALL
можно заменить их фактическими значениями и не предоставлять им никакого фактического хранилища (семантика будет той же самой), поэтому определение этих переменных в этом случае будет избыточным, следовательно, это связывает ОК.
Мне было бы трудно утверждать, что это чья-то ошибка.
Статические константные интегралы, заданные значения в точке объявления, не являются переменными, они являются константными выражениями. Чтобы существовала переменная, вам все равно нужно ее определить.
Правила относительно троичного оператора довольно абсурдно сложны, вероятно, обязательно так, и на самом деле ничего не говорят о константных выражениях, только о значениях; очевидно, компилятор считает, что они должны быть переменными, если оптимизация не улучшится. Я думаю, что это свободно интерпретировать выражение в любом случае (как константное выражение или как переменная).
Вам все еще нужно выделить место для них где-нибудь:
class MagicNumbers
{
public:
static const int BIG = 100;
static const int SMALL = 10;
};
const int MagicNumbers::BIG;
const int MagicNumbers::SMALL;
Почему ваши магические числа в классе?
namespace MagicNumbers {
const int BIG = 100;
const int SMALL = 10;
}
Проблема решена, не беспокоясь о недостатках в стандарте C++.