Страницы

Поиск по вопросам

понедельник, 4 февраля 2019 г.

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

Есть два способа сделать константное значение в классе:
Использовать перечисление:
class C { enum { X = 42; } };
Или статический член класса:
class C { static const auto X = 42; };
Какой способ предпочтительнее? Какие могут быть подводные камни?


Ответ

Статический член класса позволяет использовать вывод типов:
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(C::X)); // явный каст f(+C::X); // унарный плюс
Какой из этого можно сделать вывод? Статические арифметические константные члены классов слишком удобны чтобы от них отказываться, и если вдруг появится ошибка линковки, то достаточно использовать унарный плюс чтобы ее починить.

Комментариев нет:

Отправить комментарий