Constexpr против макросов

Где я предпочитаю использовать макросы и где я должен предпочесть constexpr? Разве они в основном не одинаковы?

#define MAX_HEIGHT 720

против

constexpr unsigned int max_height = 720;

3 ответа

Разве они в основном не одинаковы?

Абсолютно нет. Даже не близко.

Помимо того, что ваш макрос int и ваш constexpr unsigned является unsignedЕсть важные различия, и у макросов есть только одно преимущество.

Объем

Макрос определяется препроцессором и просто подставляется в код каждый раз, когда он происходит. Препроцессор тупой и не понимает синтаксис или семантику C++. Макросы игнорируют такие области, как пространства имен, классы или функциональные блоки, поэтому вы не можете использовать имя для чего-либо еще в исходном файле. Это не верно для константы, определенной как правильная переменная C++:

#define MAX_HEIGHT 720
constexpr int max_height = 720;

class Window {
  // ...
  int max_height;
};

Хорошо иметь переменную-член с именем max_height потому что это член класса и поэтому имеет другую область видимости и отличается от области видимости в пространстве имен. Если вы пытались повторно использовать имя MAX_HEIGHT для члена тогда препроцессор изменит это на эту ерунду, которая не будет компилироваться:

class Window {
  // ...
  int 720;
};

Вот почему вы должны дать макросы UGLY_SHOUTY_NAMES чтобы они выделялись, и вы можете быть осторожны, называя их, чтобы избежать столкновений. Если вы не используете макросы без необходимости, вам не нужно беспокоиться об этом (и не нужно читать SHOUTY_NAMES).

Если вам просто нужна константа внутри функции, вы не можете сделать это с помощью макроса, потому что препроцессор не знает, что такое функция или что значит быть внутри нее. Чтобы ограничить макрос только определенной частью файла, вам нужно #undef это снова:

int limit(int height) {
#define MAX_HEIGHT 720
  return std::max(height, MAX_HEIGHT);
#undef MAX_HEIGHT
}

Сравните с гораздо более разумным:

int limit(int height) {
  constexpr int max_height = 720;
  return std::max(height, max_height);
}

Почему бы вы предпочли макро?

Реальная память

Переменная constexpr - это переменная, поэтому она действительно существует в программе, и вы можете делать обычные вещи C++, такие как получение ее адреса и привязка к ней ссылки.

Этот код имеет неопределенное поведение:

#define MAX_HEIGHT 720
int limit(int height) {
  const int& h = std::max(height, MAX_HEIGHT);
  // ...
  return h;
}

Проблема в том, что MAX_HEIGHT не является переменной, поэтому для вызова std::max временный int должен быть создан компилятором. Ссылка, которая возвращается std::max может затем обратиться к этому временному, который не существует после конца этого оператора, так return h обращается к недействительной памяти.

Эта проблема просто не существует с правильной переменной, потому что она имеет фиксированное местоположение в памяти, которое не исчезает:

int limit(int height) {
  constexpr int max_height = 720;
  const int& h = std::max(height, max_height);
  // ...
  return h;
}

(На практике вы, вероятно, объявите int h не const int& h но проблема может возникнуть в более тонких контекстах.)

Условия препроцессора

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

#define MAX_HEIGHT 720
#if MAX_HEIGHT < 256
using height_type = unsigned char;
#else
using height_type = unsigned int;
#endif

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

Если вам нужна константа, которая может быть понята препроцессором, вы должны использовать препроцессор для ее определения. Если вам нужна константа для нормального кода C++, используйте обычный код C++.

Приведенный выше пример просто демонстрирует условие препроцессора, но даже этот код может избежать использования препроцессора:

using height_type = std::conditional_t<max_height < 256, unsigned char, unsigned int>;

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

Rational:

Макросы - это простая замена в коде, и по этой причине они обычно генерируют конфликты (например, макрос windows.h max против std::max). Кроме того, работающий макрос можно легко использовать другим способом, который затем вызывает странные ошибки компиляции. (например, Q_PROPERTY используется для членов структуры)

Из-за всех этих неопределенностей, это хороший стиль кода, чтобы избегать макросов, точно так же, как вы обычно избегаете gotos.

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

Отличный ответ Джонатона Уэйкли. Я бы также посоветовал вам взглянуть на ответ джогоджапана относительно разницы между const а также constexpr прежде чем вы даже подумать об использовании макросов.

Макросы тупые, но в хорошем смысле. Якобы в наше время они помогают при сборке, когда вы хотите, чтобы очень специфичные части вашего кода компилировались только при наличии определенных параметров сборки, которые "определены". Как правило, все, что означает, - это взять имя макроса, или еще лучше, давайте назовем его Triggerи добавляя такие вещи, как, /D:Trigger, -DTriggerи т. д. для используемых инструментов сборки.

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

  1. Разделы кода для аппаратного обеспечения и платформы
  2. Увеличенная многословность строений

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

#if defined VERBOSE || defined DEBUG || defined MSG_ALL
    // Verbose message-handling code here
#endif

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

#if defined GEN_3_HW && defined _WIN64
    // Windows-only special handling for 64-bit upcoming hardware
#elif defined GEN_3_HW && defined __APPLE__
    // Special handling for macs on the new hardware
#elif !defined _WIN32 && !defined __linux__ && !defined __APPLE__ && !defined __ANDROID__ && !defined __unix__ && !defined __arm__
    // Greetings, Outlander! ;)
#else
    // Generic handling
#endif
Другие вопросы по тегам