Bezdepbonusu7Программирование: статьи и ответы

Вопрос

Альтернативы использованию #define в С++? Почему это осуждается?

Автор вопроса: JoelОтветов: 5

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

Вот пример (непроверенный код, но общее представление вы поняли):

#define VERSION "1.2"

#include <string>

class Foo {
  public:
    string getVersion() {return "The current version is "+VERSION;}
};
  1. Почему этот код плохой?
  2. Есть ли альтернатива использованию #define?
  • Обратное этому вопросу: Зачем использовать #define вместо переменной Matt Ball 21.04.2012
  • define - это хитрые конструкции, которыми легко злоупотребить или неправильно использовать. но, как и goto, они могут быть очень полезными. Anycorn 21.04.2012
  • возможный дубликат static const vs #define, в частности этот ответ должно быть достаточно. Anonymous 21.04.2012
  • Сомневаюсь, что этот код компилируется. Без + было бы, но как есть? Неа. Matthieu M. 21.04.2012

c++global-variablescoding-stylec-preprocessor

Ответы

Ответ 1 · рейтинг 13 · Benjamin Lindley

Почему этот код плохой?

Потому что ВЕРСИЯ может быть перезаписана, и компилятор вам этого не скажет.

Есть ли альтернатива использованию #define?

const char * VERSION = "1.2";

or

const std::string VERSION = "1.2";

Ответ 2 · рейтинг 11 · Konrad Rudolph

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

Рассмотрим случай max, который иногда реализуется как макрос. Как следствие, вы не можете использовать идентификатор max где-либо в своем коде. Где. Но компилятор вам этого не скажет. Вместо этого ваш код будет работать ужасно неправильно, и вы понятия не имеете, почему.

Теперь, при некоторой осторожности, эту проблему можно свести к минимуму (если не полностью устранить). Но для большинства применений #define в любом случае есть лучшие альтернативы, поэтому расчет затрат/выгод становится искаженным: небольшой недостаток вместо отсутствия какой бы то ни было выгоды. Зачем использовать дефектную функцию, если она не дает никаких преимуществ?

Итак, вот очень простая схема:

  1. Нужна константа? Используйте константу (не определение)
  2. Нужна функция? Используйте функцию (не определение)
  3. Нужно что-то, что нельзя смоделировать с помощью константы или функции? Используйте определение, но делайте это правильно.

Делать это "правильно" - само по себе искусство, но есть несколько простых рекомендаций:

  1. Используйте уникальное имя. Все заглавные, всегда с префиксом уникального идентификатора библиотеки. max? Вне. VERSION? Вне. Вместо этого используйте MY_COOL_LIBRARY_MAX и MY_COOL_LIBRARY_VERSION. Например, библиотеки Boost, крупные пользователи макросов, всегда используют макросы, начинающиеся с BOOST_<LIBRARY_NAME>_.

  2. Остерегайтесь оценки. По сути, параметр в макросе - это просто заменяемый текст. Как следствие, #define MY_LIB_MULTIPLY(x) x * x сломан: его можно было использовать как MY_LIB_MULTIPLY(2 + 5), в результате чего получилось 2 + 5 * 2 + 5. Не то, что мы хотели. Чтобы избежать этого, всегда заключайте в скобки все использование аргументов (если только вы не знаете, точно что делаете - спойлер: скорее всего, вы не знаете, что делаете). т; даже эксперты тревожно часто ошибаются в этом).

    Правильная версия этого макроса будет:

     #define MY_LIB_MULTIPLY(x) ((x) * (x))
    

Но есть по-прежнему множество способов сделать макросы ужасно неправильными, и, повторяю, компилятор здесь вам не поможет.

Ответ 3 · рейтинг 4 · Kyle

#define не является плохим по своей сути, им просто легко злоупотреблять. Для чего-то вроде строки версии это работает нормально, хотя const char* было бы лучше, но многие программисты используют его для гораздо большего. Например, использование #define в качестве определения типа глупо, когда в большинстве случаев определение типа было бы лучше. Так что в операторах #define нет ничего плохого, и некоторые вещи невозможно сделать без них. Их необходимо оценивать в каждом конкретном случае. Если вы можете найти способ решить проблему без использования препроцессора, вы должны это сделать.

Ответ 4 · рейтинг 2 · EdChum

Я бы не стал использовать #define для определения постоянного ключевого слова use static или еще лучше const int kMajorVer = 1; const int kMinorVer = 2; ИЛИ const std::string kVersion = "1.2";

У Херба Саттера есть отличная статья, в которой подробно описывается, почему #define - это плохо, и приводится несколько примеров, где действительно нет другого способа добиться того же самого: http://www.gotw.ca/gotw/032.htm.

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

Я лично использую их для кода условной отладки, а также для вариантов представления данных, которые подробно описаны в конце статьи о саттере.

Ответ 5 · рейтинг 1 · Andrew Tomazos

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

const char* VERSION = "1.2"

Однако есть случаи, когда невозможно сделать то, что вы хотите, без препроцессора:

#define Log(x) cout << #x << " = " << (x) << endl;

Перевод вопроса и ответов сообщества Stack Overflow, лицензия CC BY-SA. Оригинал на Stack Overflow.

Похожие материалы