const vs constexpr для переменных в C++: в чем разница и что выбрать?
Ключевое отличие: время вычисления vs гарантия неизменности
Давайте разберем на примерах. Главное различие — constexpr требует, чтобы значение было известно на этапе компиляции, а const только запрещает изменение переменной после инициализации. При этом const может быть инициализирован значением, которое вычисляется во время выполнения.
// Пример 1: const с runtime-значением
int getUserAge() { return 25; } // обычная функция
const int age = getUserAge(); // OK: age неизменяема, но значение известно только в runtime
// Пример 2: constexpr требует compile-time
constexpr int compileTimeAge = getUserAge(); // ОШИБКА: getUserAge() не constexpr
Практические сценарии использования
Используйте const для:
- API и интерфейсов — когда переменная не должна изменяться, но её значение может быть результатом вызова функции (например, размер вектора).
- Параметров функций —
const int& valueзащищает от случайного изменения. - Контейнеров и объектов —
const std::string name = getName();
Используйте constexpr для:
- Размеров массивов — в стандартном C++ размер массива должен быть константой времени компиляции.
- Шаблонных параметров —
std::array<int, size>требуетconstexpr. - Математических констант —
constexpr double pi = 3.141592653589793; - Флагов компиляции —
constexpr bool debugMode = false;
Почему const для массивов может работать (но не всегда)
Вопрос про int arr[size] — это ловушка. В C++ стандартные массивы требуют constexpr размера. Однако некоторые компиляторы (например, GCC с расширениями) поддерживают VLA (Variable Length Arrays) — это нестандартное поведение.
// Правильный C++:
constexpr int size = 10;
int arr[size]; // OK: size известен на этапе компиляции
// Некорректный C++ (но может скомпилироваться с расширениями):
int runtimeSize = 10;
const int size2 = runtimeSize;
int arr2[size2]; // Ошибка в стандартном C++ (VLA — нестандартное расширение)
Когда constexpr строже — это хорошо
Если вы можете сделать переменную constexpr — делайте. Это даёт:
- Гарантию нулевого оверхеда — значение встраивается в код.
- Возможность использовать в контекстах, требующих compile-time (например,
static_assert, шаблоны). - Оптимизацию — компилятор может вычислить больше на этапе компиляции.
Итоговое правило
Простое эмпирическое правило: если значение может быть вычислено во время компиляции — используйте constexpr. Если нет — используйте const. Не бойтесь комбинировать: constexpr const int x = 5; — избыточно, но не ошибочно (в C++17 constexpr уже подразумевает const).
Пример из реального кода:
class Config {
public:
static constexpr double PI = 3.14159; // compile-time
static const std::string APP_NAME = "MyApp"; // const, т.к. std::string не constexpr
};
<<Дополнение: constexpr в C++17 и далее
Стоит отметить, что в C++17 constexpr стало более гибким. Теперь можно объявлять constexpr лямбды и даже constexpr if. А в C++20 появились constexpr виртуальные функции и constexpr динамическая память (ограниченно).
Ещё один важный момент: constexpr переменные по умолчанию имеют внутреннюю линковку (internal linkage), если они не объявлены как extern. Это значит, что вы можете определить constexpr int x = 5; в заголовочном файле без риска множественного определения — каждый translation unit получит свою копию. Для const переменных такое поведение тоже работает, но только если они инициализированы константным выражением (с C++17).
Практический совет: если вы пишете библиотеку или хедер-онли код — используйте constexpr для числовых констант. Это безопаснее и даёт больше возможностей для оптимизации. Если же константа зависит от рантайма (например, результат вызова clock()) — только const.
Ответов пока нет.
Войдите чтобы оставить ответ