Целочисленная константа в классе - enum или static const?

Рейтинг: 6Ответов: 1Опубликовано: 27.04.2015

Есть два способа сделать константное значение в классе:

Использовать перечисление:

class C {
  enum { X = 42; }
};

Или статический член класса:

class C {
  static const auto X = 42;
};

Какой способ предпочтительнее? Какие могут быть подводные камни?

Ответы

▲ 8Принят

Статический член класса позволяет использовать вывод типов:

static const auto X = 1ul;

Для enum тип надо писать самому (если он не int):

enum { X = 1 };
enum : unsigned long { Y = 1 };

Получается что статический член класса выглядит предпочтительнее, к тому же он семантически является константой, а перечисление больше рассчитано на "перечисление" нескольких значений.

Подводные камни

Если мы попробуем скомпилировать следующий код

struct C {
    static const auto X = 1;
};

int f(const int& arg) {
    return arg;
}

int main() {
    return f(C::X);
}

То мы можем получить ошибку линковки (при отключенном оптимизаторе):

main.cpp:(.text+0x15): undefined reference to `C::X'

Оказывается, статические целочисленные константные члены классов не требуют отдельного определения только если они не ODR-использованы (odr-used). Мы берем ссылку на C::X, это делает ее odr-used, и значит мы должны добавить ее определение вне класса:

struct C {
    static const auto X = 1;
};

const int C::X; // В одном из .cpp файлов.

Термин "odr-used" определен в главе "One definition rule [basic.def.odr]" следующим образом:

Переменная, чье имя использовано в потенциально вычисляемом контексте (т.е. не sizeof или decltype), является odr-used, за исключением случая когда она является объектом, который может быть использован в константном выражении, и к ней сразу же применяется преобразование из lvalue в rvalue.

В нашем случае C::X может быть использована в константном выражении, однако она является lvalue и передается в f() как ссылка на lvalue. Если мы добавим преобразование в rvalue, то отдельное определение C::X уже будет не нужно, и ошибка линковки пропадет:

f(static_cast<int>(C::X));  // явный каст
f(+C::X); // унарный плюс

Какой из этого можно сделать вывод?
Статические арифметические константные члены классов слишком удобны чтобы от них отказываться, и если вдруг появится ошибка линковки, то достаточно использовать унарный плюс чтобы ее починить.