Переносите вычисления из времени выполнения во время компиляции
Причина
Чтобы уменьшить размер кода и время выполнения. Чтобы избежать гонок данных за счёт использования констант. Чтобы выявлять ошибки на этапе компиляции (и тем самым устранять необходимость в коде обработки ошибок).
Пример
double square(double d) { return d*d; }
static double s2 = square(2); // старый стиль: динамическая инициализация
constexpr double ntimes(double d, int n) // предполагается 0 <= n
{
double m = 1;
while (n--) m *= d;
return m;
}
constexpr double s3 {ntimes(2, 3)}; // современный стиль: инициализация во время компиляции
Код, подобный инициализации s2, не редкость, особенно когда инициализация чуть сложнее square(). Однако по сравнению с инициализацией s3 есть два недостатка:
- во время выполнения несём накладные расходы на вызов функции
- к
s2может обратиться другой поток до завершения инициализации.
Замечание: для константы гонки данных невозможны.
Пример
Рассмотрим популярный приём хранения небольших объектов непосредственно в дескрипторе, а больших — в куче.
constexpr int on_stack_max = 20;
template<typename T>
struct Scoped { // хранить T в Scoped
// ...
T obj;
};
template<typename T>
struct On_heap { // хранить T в свободном хранилище
// ...
T* objp;
};
template<typename T>
using Handle = typename std::conditional<(sizeof(T) <= on_stack_max),
Scoped<T>, // первая альтернатива
On_heap<T> // вторая альтернатива
>::type;
void f()
{
Handle<double> v1; // double размещается на стеке
Handle<std::array<double, 200>> v2; // массив размещается в свободном хранилище
// ...
}
Предположим, что Scoped и On_heap предоставляют совместимые пользовательские интерфейсы. Здесь мы вычисляем оптимальный тип для использования во время компиляции. Существуют аналогичные приёмы для выбора оптимальной функции для вызова.
Примечание
Идеал — не пытаться выполнить всё во время компиляции. Очевидно, большинство вычислений зависят от входных данных и не могут быть перенесены в компиляцию, но помимо этого логического ограничения существует и то, что сложные вычисления во время компиляции могут серьёзно увеличить время компиляции и усложнить отладку. Код даже можно замедлить за счёт компиляторных вычислений. Это редкость, но разбив общее вычисление на отдельные оптимальные подвычисления, можно снизить эффективность кэша инструкций.
Контроль
- Ищите простые функции, которые могли бы быть
constexpr(но не являются ими). - Ищите функции, вызываемые только с аргументами — константными выражениями.
- Ищите макросы, которые можно заменить на
constexpr.