Где я предпочитаю использовать макросы и где я должен предпочесть constexpr? Разве они в основном не одинаковы?
#define MAX_HEIGHT 720
против
constexpr unsigned int max_height = 720;
Где я предпочитаю использовать макросы и где я должен предпочесть constexpr? Разве они в основном не одинаковы?
#define MAX_HEIGHT 720
против
constexpr unsigned int max_height = 720;
Разве они не в основном одинаковы?
Нет. Абсолютно нет. Даже не близко.
Помимо того факта, что ваш макрос является 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
not 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
всякий раз, когда можете, и макросы, только если никакое другое решение невозможно.
Макросы - это простая замена в коде, и по этой причине они часто вызывают конфликты (например, макрос windows.h max
против std::max
). Кроме того, работающий макрос можно легко использовать другим способом, который затем может вызвать странные ошибки компиляции. (например, Q_PROPERTY
используется для элементов структуры)
Из-за всех этих неопределенностей, это хороший стиль кода, чтобы избегать макросов, точно так же, как вы обычно избегаете gotos.
constexpr
определяется семантически, и, таким образом, обычно создает гораздо меньше проблем.
Отличный ответ Джонатана Вакли. Я также советую вам взглянуть на jogojapan ответ о том, какая разница между const
и constexpr
прежде чем вы даже начнете рассматривать использование макросов.
Макросы тупые, но в хорошем смысле. Якобы в настоящее время они являются сборкой, когда вы хотите, чтобы очень специфические части вашего кода были скомпилированы только при наличии определенных параметров сборки, которые были "определены". Обычно это означает, что ваше макросовое имя или, еще лучше, позвольте ему -DTrigger
Trigger
и добавьте в инструменты построения такие вещи, как: /D:Trigger
, -DTrigger
и т.д.
Хотя для макросов много разных применений, это те два, которые я вижу чаще всего, которые не являются плохими/устаревшими:
Таким образом, хотя вы можете в случае OP выполнить ту же цель определения int с 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