В версии C++11 появились константные выражения constexpr (constant expression) - этот модификатор подсказывает компилятору, что сущность может быть выполнена и вычислена на этапе компиляции, то есть буквально силами компилятора, а не скомпилированной и запущенной программы. Посмотрим, как оно работает.
Когда код выполняется компилятором, то это происходит в compile time - на этапе компиляции. Если код выполняется непосредственно во время работы программы - это называется runtime, по-русски так и называют - в рантайме
Константы и выражения #
constexpr-выражение может содержать только члены, значение которых известно на момент компиляции, и операции между ними:
- Целые и
floatлитералы (непосредственно цифры в коде),bool,nullptr, строковые литералы ("строки") - Другие
constexpr-константы - Вызов (результат)
constexpr-функций и методов классов - Численные параметры шаблонов
#define-константы с такими же правилами- Математические, логические и бинарные операции
Такое выражение может быть присвоено constexpr константе:
#define MY_CONST 3.14f
constexpr int myconstexpr = 5;
constexpr int cnstFunc() {
return 10;
}
constexpr int myconst = myconstexpr + 123 * MY_CONST * cnstFunc();
Отличие от обычной константы состоит в том, что выражение в любом случае вычисляется компилятором, т.е. даже не попадает в программу после компиляции - только результат в виде числа. Современные IDE, например VS Code (урок), подсвечивают результат, если навести курсор на имя константы - очень удобно, можно отлаживать некоторые конструкции даже не компилируя программу, не говоря о том, чтобы её запускать.
Константы в классе #
В классе можно использовать static constexpr-константы вместо глобальных #define-констант - они не займут места в памяти, но сделают код более читаемым и безопасным, т.к. имена будут инкапсулированы внутри класса:
class Foo {
static constexpr int FOO_CONST = 123;
public:
void foo() {
int i = FOO_CONST * 10;
}
};
Эти константы также могут зависеть от параметров шаблона, что избавляет от лишнего кода, если в классе используются математические выражения с парметром шаблона. Например, задаётся размер данных, а в программе нам нужно будет количество блоков по 8 с округлением вверх. Это можно организовать вот так:
template <int size>
class Foo {
static constexpr int FOO_CHUNKS = (size + 8 - 1) / 8;
public:
};
Теперь у каждого объекта с разным значением параметра будет своя константа, оптимизированная компилятором.
Функции (C++11) #
constexpr функция может быть вычислена компилятором, если вызывается с известными на момент компиляции аргументами. В то же время, она может вызываться и выполняться как обычная функция в рантайме.
В разных версиях C++ есть разные ограничения на сам код таких функций: чем старше версия - тем меньше ограничений. Рассмотрим версию C++11, т.к. в основном она используется по умолчанию для разработки под МК (AVR, ESP) на момент написания урока. constexpr функция:
- Должна содержать только один
return - Не может содержать
for,while,do,if,switch,goto - Не может содержать вызовов других не-
constexprфункций - Не может создавать локальные переменные
- Не может работать с динамической памятью (
new,delete)
А что же тогда она вообще может и как с этим работать? Можно:
- Вызывать другие
constexprфункции и классы - Использовать рекурсивный вызов вместо циклов
- Использовать параметры функции вместо локальных переменных
- Использовать тернарный оператор для ветвления
Чтобы писать более сложный код в таких ограниченных условиях, нужно хорошо освоить рекурсивные конструкции. Например, перепишем для constexpr показанные в том уроке функции. По сути, нужно было просто заменить условие на тернарник:
constexpr int factorial(int n) {
return n ? (n * factorial(n - 1)) : 1;
}
constexpr int power(int base, int exp) {
return exp ? (base * power(base, exp - 1)) : 1;
}
Вот ещё например функция, позволяющая найти логарифм по основанию 2, т.е. найти число, в которое нужно возвести 2 для получения заданного значения с округлением вниз:
constexpr uint8_t log2(uint32_t v, uint8_t i = 0) {
return v ? log2(v >> 1, i + 1) : (i ? i - 1 : 0);
}
Может использоваться для хитрых вычислений с оптимизацией деления сдвигом.
constexpr - полезный инструмент, но становится ещё более полезным в свежих версиях C++. Например вот проект constexpr CSV парсера на C++17.
Возможности в версиях #
| Возможность | C++11 | C++14 | C++17 | C++20 | C++23 |
|---|---|---|---|---|---|
Простые выражения (арифметика, return) |
✅ | ✅ | ✅ | ✅ | ✅ |
Вызовы других constexpr функций |
✅ | ✅ | ✅ | ✅ | ✅ |
Шаблонные constexpr функции |
✅ | ✅ | ✅ | ✅ | ✅ |
Использование классов с constexpr членами |
✅ | ✅ | ✅ | ✅ | ✅ |
| Локальные переменные | ❌ | ✅ | ✅ | ✅ | ✅ |
Условные операторы (if, switch) |
❌ | ✅ | ✅ | ✅ | ✅ |
Циклы (for, while, do) |
❌ | ✅ | ✅ | ✅ | ✅ |
| Вложенные функции | ❌ | ✅ | ✅ | ✅ | ✅ |
Использование try/catch |
❌ | ❌ | ❌ | ✅ | ✅ |
Динамическое выделение памяти (new, delete) |
❌ | ❌ | ❌ | ✅ | ✅ |
| Использование виртуальных функций | ❌ | ❌ | ❌ | ✅ | ✅ |
Работа с std::vector, std::string |
❌ | ❌ | ❌ | ❌ | ✅ |
Полезные страницы #
- Набор GyverKIT – наш большой стартовый набор Arduino, продаётся в России
- Каталог ссылок на дешёвые Ардуины, датчики, модули и прочие железки с AliExpress
- Обратная связь – сообщить об ошибке в уроке или предложить дополнение по тексту ([email protected])
- Поддержать автора за работу над уроками


