статическая константа против #define - PullRequest
189 голосов
/ 28 октября 2009

Лучше ли использовать static const переменную, чем #define препроцессор? А может это зависит от контекста?

Каковы преимущества / недостатки каждого метода?

Ответы [ 10 ]

221 голосов
/ 01 октября 2010

Плюсы и минусы ко всему, в зависимости от использования:

  • перечислений
    • возможно только для целочисленных значений
    • правильно определена область видимости / проблема столкновения идентификаторов, особенно в перечислимых классах C ++ 11, где перечисления для enum class X устранены неоднозначностью из области действия X::
    • строго типизированный, но с достаточно большим размером int со знаком или без знака, над которым у вас нет контроля в C ++ 03 (хотя вы можете указать битовое поле, в которое они должны быть упакованы, если перечисление является членом struct / class / union), в то время как C ++ 11 по умолчанию int, но может быть явно установлен программистом
    • не может взять адрес - его нет, так как значения перечисления эффективно подставляются inline в точках использования
    • более строгие ограничения использования (например, приращение - template <typename T> void f(T t) { cout << ++t; } не будет компилироваться, хотя вы можете заключить перечисление в класс с неявным конструктором, оператором приведения и пользовательскими операторами)
    • тип каждой константы, взятый из вмещающего перечисления, поэтому template <typename T> void f(T) получают различные экземпляры, когда передают одно и то же числовое значение из различных перечислений, все из которых отличаются от любого фактического экземпляра f(int). Объектный код каждой функции может быть идентичным (без учета смещения адресов), но я не ожидаю, что компилятор / компоновщик удалит ненужные копии, хотя вы можете проверить свой компилятор / компоновщик, если вам это нужно.
    • даже при использовании typeof / decltype нельзя ожидать, что numeric_limits предоставит полезную информацию о наборе значимых значений и комбинаций (действительно, «допустимые» комбинации даже не указаны в исходном коде, рассмотрим enum { A = 1, B = 2 } - is A|B "законно" с точки зрения логики программы?)
    • имя типа enum может появляться в различных местах в RTTI, сообщениях компилятора и т. Д. - возможно, полезно, возможно, запутывание
    • вы не можете использовать перечисление, когда единица трансляции фактически не видит значение, что означает, что перечисления в библиотечных API требуют значений, представленных в заголовке, а make и другие инструменты перекомпиляции на основе меток времени будут вызывать перекомпиляцию клиента, когда они изменились (плохо!)
  • consts
    • правильно определена область действия / проблема с идентификатором.
    • сильный, одиночный, указанный пользователем тип
      • вы можете попытаться «набрать» #define ala #define S std::string("abc"), но константа избегает повторного построения различных временных в каждой точке использования
    • Осложнения одного правила определения
    • может брать адрес, создавать на него постоянные ссылки и т. Д.
    • наиболее похоже на значение, отличное от const, которое сводит к минимуму работу и воздействие при переключении между двумя
    • значение может быть помещено в файл реализации, позволяя локализованную перекомпиляцию и просто ссылки клиента, чтобы получить изменения
  • определяет
    • «глобальная» область действия / более склонна к конфликтным использованиям, что может приводить к трудно разрешаемым проблемам компиляции и неожиданным результатам во время выполнения, а не к нормальным сообщениям об ошибках; смягчение этого требует:
      • длинные, неясные и / или централизованно координируемые идентификаторы, и доступ к ним не может быть извлечен из неявного соответствия используемого / текущего / искомого Кенига пространства имен, псевдонимов пространства имен и т. Д.
      • в то время как передовая практика позволяет шаблонам идентификаторов параметров быть односимвольными заглавными буквами (возможно, за которыми следует число), другое использование идентификаторов без строчных букв традиционно зарезервировано для ожидаемого определения препроцессора (вне ОС и C) / Заголовки библиотеки C ++). Это важно для того, чтобы использование препроцессора масштаба предприятия оставалось управляемым. Ожидается, что сторонние библиотеки будут соответствовать. Наблюдение этого подразумевает миграцию существующих констант или перечислений в / из определений, включает изменение в заглавных буквах и, следовательно, требует редактирования исходного кода клиента, а не «простой» перекомпиляции. (Лично я пишу с заглавной буквы первую букву перечислений, но не константы, поэтому мне придется переходить между этими двумя тоже - возможно, пришло время переосмыслить это.)
    • возможно больше операций во время компиляции: конкатенация строкового литерала, строковое преобразование (принимая его размер), конкатенация в идентификаторы
      • Недостатком является то, что с учетом #define X "x" и некоторого использования клиента аля "pre" X "post", если вы хотите или должны сделать X изменяемой во время выполнения переменной, а не константой, которую вы принудительно редактируете в клиентском коде (а не просто перекомпилируете), тогда как этот переход легче от const char* или const std::string, поскольку они уже заставляют пользователя включать операции объединения (например, "pre" + X + "post" для string)
    • не может использовать sizeof непосредственно для определенного числового литерала
    • нетипизированный (GCC не предупреждает по сравнению с unsigned)
    • некоторые цепочки компилятора / компоновщика / отладчика могут не предоставлять идентификатор, поэтому вы будете вынуждены смотреть на "магические числа" (строки, что угодно ...)
    • не может взять адрес
    • подставляемое значение не обязательно должно быть допустимым (или дискретным) в контексте, где создается #define, так как оно оценивается в каждой точке использования, поэтому вы можете ссылаться на еще не объявленные объекты, в зависимости от "реализации", которая не нужно предварительно включать, создавайте «константы», такие как { 1, 2 }, которые можно использовать для инициализации массивов, или #define MICROSECONDS *1E-6 и т. д. ( определенно не рекомендует это!)
    • некоторые специальные вещи, такие как __FILE__ и __LINE__, могут быть включены в подстановку макросов
    • вы можете проверить наличие и значение в #if операторах для условного включения кода (более мощный, чем пост-препроцессор «если», так как код не нужно компилировать, если не выбран препроцессором), используйте #undef - Ин, переопределить и т. д.
    • подставляемый текст должен быть выставлен:
      • в единице перевода, которой он используется, что означает, что макросы в библиотеках для использования клиентом должны быть в заголовке, поэтому make и другие инструменты перекомпиляции на основе временных меток будут вызывать перекомпиляцию клиента при их изменении (плохо!)
      • или в командной строке, где требуется еще больше усилий, чтобы убедиться, что клиентский код перекомпилирован (например, Makefile или скрипт, предоставляющий определение, должны быть указаны как зависимость)

Как правило, я использую const s и считаю их наиболее профессиональным вариантом для общего пользования (хотя другие имеют простоту, привлекательную для этого старого ленивого программиста).

129 голосов
/ 28 октября 2009

Лично я ненавижу препроцессор, поэтому всегда буду использовать const.

Основным преимуществом #define является то, что он не требует памяти для хранения в вашей программе, поскольку в действительности он просто заменяет некоторый текст буквальным значением. Он также имеет то преимущество, что у него нет типа, поэтому его можно использовать для любого целочисленного значения без генерации предупреждений.

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

Я не знаю точно, что вы получаете с "статической" частью, хотя. Если вы декларируете глобально, я бы поместил его в автономное пространство имен вместо использования static. Например

namespace {
   unsigned const seconds_per_minute = 60;
};

int main (int argc; char *argv[]) {
...
}
42 голосов
/ 28 октября 2009

Если это вопрос C ++ и в качестве альтернативы упоминается #define, то речь идет о «глобальных» (т. Е. File-scope) константах, а не о членах класса. Когда дело доходит до таких констант в C ++ static const является избыточным. В C ++ const по умолчанию есть внутренние связи, и нет смысла объявлять их static. Так что это действительно около const против #define.

И, наконец, в C ++ const предпочтительнее. Хотя бы потому, что такие константы типизированы и ограничены. Нет никаких причин предпочитать #define над const, за исключением нескольких исключений.

Строковые константы, кстати, являются одним из примеров такого исключения. С #define d строковыми константами можно использовать функцию конкатенации во время компиляции компиляторов C / C ++, как в

#define OUT_NAME "output"
#define LOG_EXT ".log"
#define TEXT_EXT ".txt"

const char *const log_file_name = OUT_NAME LOG_EXT;
const char *const text_file_name = OUT_NAME TEXT_EXT;

P.S. Опять же, на всякий случай, когда кто-то упоминает static const как альтернативу #define, это обычно означает, что они говорят о C, а не о C ++. Интересно, правильно ли помечен этот вопрос ...

5 голосов
/ 28 октября 2009

Использование статического const похоже на использование любых других константных переменных в вашем коде. Это означает, что вы можете отслеживать, откуда поступает информация, в отличие от #define, который будет просто заменен в коде в процессе предварительной компиляции.

Возможно, вы захотите взглянуть на C ++ FAQ Lite по этому вопросу: http://www.parashift.com/c++-faq-lite/newbie.html#faq-29.7

4 голосов
/ 28 октября 2009
  • Статический констант набирается (он имеет тип) и может быть проверен компилятором на правильность, переопределение и т. Д.
  • a #define может быть повторно определен неопределенным.

Обычно вам следует отдавать предпочтение статическим концам. Это не имеет недостатка. Prprocessor должен в основном использоваться для условной компиляции (а иногда и для действительно грязных трюков).

2 голосов
/ 23 апреля 2013

Определение констант с помощью директивы препроцессора #define не рекомендуется применять не только в C++, но и в C. Эти константы не будут иметь тип. Еще в C было предложено использовать const для констант.

2 голосов
/ 28 октября 2009

Пожалуйста, смотрите здесь: статическая константа против определения

обычно объявление const (обратите внимание, что оно не должно быть статичным) - это путь, по которому

1 голос
/ 24 марта 2019

#define может привести к неожиданным результатам:

#include <iostream>

#define x 500
#define y x + 5

int z = y * 2;

int main()
{
    std::cout << "y is " << y;
    std::cout << "\nz is " << z;
}

Выводит неверный результат:

y is 505
z is 510

Однако, если вы замените это на константы:

#include <iostream>

const int x = 500;
const int y = x + 5;

int z = y * 2;

int main()
{
    std::cout << "y is " << y;
    std::cout << "\nz is " << z;
}

Выводит правильный результат:

y is 505
z is 1010

Это потому, что #define просто заменяет текст. Поскольку это может серьезно испортить порядок операций, я бы рекомендовал вместо этого использовать постоянную переменную.

1 голос
/ 25 октября 2017

Всегда предпочитайте использовать языковые функции перед некоторыми дополнительными инструментами, такими как препроцессор.

ES.31: не использовать макросы для констант или "функций"

Макросы являются основным источником ошибок. Макросы не подчиняются обычному объему и введите правила. Макросы не подчиняются обычным правилам аргументации прохождение. Макросы гарантируют, что читатель видит что-то другое из того, что видит компилятор. Макросы усложняют создание инструмента.

Из Основные положения C ++

0 голосов
/ 07 июля 2015

Если вы определяете константу, которая будет использоваться всеми экземплярами класса, используйте static const. Если константа специфична для каждого экземпляра, просто используйте const (но учтите, что все конструкторы класса должны инициализировать эту переменную-член const в списке инициализации).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...