Интегральные константы С++ + оператор выбора = проблема!

Недавно я обнаружил раздражающую проблему в какой-то большой программе, которую я разрабатываю; я хотел бы понять, как исправить это наилучшим образом. Я сократил код до следующего минимального примера.

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

Проблема решена без необходимости беспокоиться о недостатках в стандарте С++.