Интегральные константы С++ + оператор выбора = проблема!
Недавно я обнаружил раздражающую проблему в какой-то большой программе, которую я разрабатываю; я хотел бы понять, как исправить это наилучшим образом. Я сократил код до следующего минимального примера.
#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): undefined ссылка на `MagicNumbers:: SMALL '
test.cpp:(. text + 0xb3): undefined ссылка на `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 для этих констант, а перечисление является синонимом int). Но я действительно хочу спросить: чья ошибка?
А я виноват в том, что не понимаю, как работают статические интегральные константы?
Должен ли я обвинять gcc и надеяться на исправление (или, возможно, у последней версии есть исправление, или, может быть, есть неясный аргумент командной строки, чтобы сделать эту работу)?
Между тем, я просто компилирую свой код с оптимизацией, и это боль для отладки: -O3
Ответы
Ответ 1
Несмотря на обычные рекомендации, я обнаружил, что static const int ...
неизменно дает мне больше головных болей, чем старый добрый enum { BIG = 100, SMALL = 10 };
. И с С++ 11, предоставляющим строго типизированные перечисления, теперь у меня есть еще меньше причин использовать static const int ...
.
Ответ 2
Это известная проблема. Стандарт виноват или вы не даете определения статики. В зависимости от вашей точки зрения:)
Ответ 3
Элементы статических данных не работают так, как это в С++:
Элементы статических данных не являются частью объекты данного типа класса; Oни являются отдельными объектами. В результате объявление статического члена данных не считается определением. Данные член объявляется в классе, но определение выполняется в области файлов. Эти статические элементы имеют внешние связь.
Вы только объявляете эти константы, даже если вы их инициализируете. Вы все же должны определить их в области пространства имен:
class MagicNumbers
{
public:
static const int BIG = 100;
static const int SMALL = 10;
};
const int MagicNumbers::BIG;
const int MagicNumbers::SMALL;
Это избавит вас от ошибок ссылок.
Ответ 4
Хех, согласно стандарту С++, 9.4.2 (class.static.data):
Если статический член данных имеет const буквальный тип, его объявление в определение класса может указывать скользящий или равный-инициализатор, в котором каждый параметр-инициализатор, который является присваивание-выражение является константой выражение. Статический член данных буквальный тип может быть объявлен в определение класса с помощью constexpr спецификатор; если да, то его выражение укажет скользящий или равный-инициализатор, в котором каждый параметр-инициализатор, который является присваивание-выражение является константой выражение. [Примечание: В обоих этих случаев, член может появиться в постоянные выражения. -end note] член должен быть определен в область пространства имен, если она используется в программы и области пространства имен определение не должно содержать инициализатор.
Итак, объявление правильное, но вам все равно нужно иметь определение. Я всегда думал, что вы можете применить определение, но я полагаю, что это не стандартное соответствие.
Ответ 5
Мне было бы трудно утверждать, что это ошибка.
Статические константы-константы, заданные значения в точке объявления, не являются переменными, они являются постоянными выражениями. Чтобы быть переменной, вам все равно нужно ее определить.
Правила по тернарному оператору довольно абсурдно сложны, возможно, обязательно так, и на самом деле ничего не говорят о постоянных выражениях, только rvalues; очевидно, что компилятор считает, что они должны быть переменными, если оптимизация не будет развита. Я считаю, что это свободно интерпретировать выражение в любом случае (как постоянное выражение или как переменная).
Ответ 6
Я новичок в С++, но я думаю, что ваше объявление класса объявляет только, что эти статические члены существуют, вам все равно нужно их где-то определить:
class MagicNumbers
{
public:
static const int BIG;
static const int SMALL;
};
const int MagicNumbers::BIG = 100;
const int MagicNumbers::SMALL = 10;
Более высокие уровни оптимизации, вероятно, включают в себя уровень статического анализа, достаточно тщательный, чтобы определить, что BIG
и SMALL
можно обменивать с их фактическими значениями, а не давать им какое-либо фактическое хранилище (семантика будет одинаковой) поэтому определение этих переменных в этом случае было бы излишним, поэтому оно связывает ОК.
Ответ 7
Вам все равно нужно выделить место для них где-нибудь:
class MagicNumbers
{
public:
static const int BIG = 100;
static const int SMALL = 10;
};
const int MagicNumbers::BIG;
const int MagicNumbers::SMALL;
Ответ 8
Почему ваши магические числа в классе?
namespace MagicNumbers {
const int BIG = 100;
const int SMALL = 10;
}
Проблема решена без необходимости беспокоиться о недостатках в стандарте С++.