Ответ 1
Разве они не в основном одинаковы?
Нет. Абсолютно нет. Даже не близко.
Помимо того факта, что ваш макрос является 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>;