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

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

вот пример (непроверенный код, но вы получаете общую идею):

#define VERSION "1.2"

#include <string>

class Foo {
  public:
    string getVersion() {return "The current version is "+VERSION;}
};
  1. почему этот код плохо?
  2. есть ли альтернатива использованию #define?

5 ответов


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

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

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

const char * VERSION = "1.2";

или

const std::string VERSION = "1.2";

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

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

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

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

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

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

  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. Не то, что мы хотели. Чтобы защититься от этого, всегда parenhesise все использование аргументов (если вы не знаете ровно то, что вы делаете – спойлер: вы, вероятно, нет; даже эксперты получают это неправильно тревожно часто).

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

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

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


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


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

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

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

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


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

const char* VERSION = "1.2"

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

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