Сьогодні на порядку денному — очевидний, але часто не використовуваний підхід: Негайно Викликані Лямбда Вирази (IILE).
Чому важливий const
?
Незмінність є ключовим принципом для надійного і зрозумілого коду. Використання const
, де це можливо, допомагає компілятору виявляти помилки та чітко вказує на намір програміста. Коли інша людина читає ваш код і бачить const
, це значно знижує її когнітивне навантаження — "немає потреби відслідковувати зміни цієї змінної — все ясно". Важливість правильного використання const
є основою багатьох хороших практик у C++.
Проблема
У простих випадках ініціалізація констант не викликає труднощів:
const int cmaxPlayers = 100;
const double cscale = getScaleFactor() * 1.5;
const bool cenabled = check() || FORCEENABLE;
const int c_healthModifier = bHealing ? 20 : 0;
Але що робити, коли обчислення значення для константи вимагає кілька кроків, тимчасових змінних, циклів або умов?
float ccalculatedDamage = getBaseDamageValue();
if (targetAimed(calculatedDamage)) {
for (int i = 0; i < ceffectCount; ++i) {
calculatedDamage += getBonusDamage(i);
}
}
Традиційні підходи — винесення логіки в окрему функцію або зняття const
— не завжди ідеальні. Винесення в окрему функцію може бути надмірним, якщо логіка використовується лише один раз. Зняття const
знижує безпеку і виразність коду.
Негайно Викликаний Лямбда Вираз (IILE)
У таких випадках на допомогу приходить Негайно Викликаний Лямбда Вираз (IILE). Ми визначаємо лямбда-функцію, яка інкапсулює всю складну ініціалізаційну логіку, і негайно її викликаємо. Результат цього виклику присвоюється нашій константі.
Ось як це працює:
const auto myLambda = { return 13; }();
Усе залежить від дужок 🤪 Останні дужки ()
після лямбди — це негайний виклик, який змушує лямбду виконуватися одразу після її визначення. Це дозволяє ініціалізувати складний об'єкт та зберегти його незмінність:
const auto ccalculatedDamage = & {
float tempDamage = getBaseDamageValue();
if (targetAimed(tempDamage)) {
for (int i = 0; i < ceffectCount; ++i) {
tempDamage += getBonusDamage(i);
}
}
return tempDamage;
}();
Переваги IILE для ініціалізації
🟢 Інкапсуляція — Уся ініціалізаційна логіка зібрана в одному місці.
🟢 Локальність — Тимчасові змінні, що використовуються для обчислення, не впливають на зовнішнє середовище.
🟢 Правильність const
— Можливість оголосити змінну як const
(або навіть constexpr
, якщо лямбда відповідає вимогам), навіть якщо її обчислення складається з кількох кроків.
🟢 Чистота коду — Виключає необхідність створювати одноразові функції.
Альтернативний синтаксис (C++17)
В C++17 можна використовувати std::invoke
, хоча для IILE прямий виклик ()
зазвичай є кращим і зрозумілішим:
include
// ...
const auto c_anotherConstant = std::invoke([] {
// ... logic ...
return 13;
});
Джерела
🔗 ES.28: Використовуйте лямбди для складної ініціалізації, особливо для const змінних
Перекладено з: A modular logging library for C++23