Розвиваючи думку про кешуванні результату обчислень важких функцій , пропоную написати невеликий шаблонний клас, який буде як аргумент конструктура приймати фунцию, а саме лямбда функцію, як найбільш універсальний інструмент, всередині якої буде виконуватися важка функція.
При цьому результат функції буде розраховуватися не в момент створення функтором, а в момент виклику оператора круглийх дужок () . І при цьому отриманий результат буде кешуватися. Що дозволить не викликати важку функцію більше ніж один раз в рамках виконання якого-небудь методу.
Вступ
Звичайно, можна спочатку виконати важку функцію і зберегти результат в змінну, але якщо в логіці вашої програми не потрібно відразу розраховувати потрібне значення, а лише в тому випадку, коли воно дійсно потрібно. То тоді немає необхідності взагалі викликати цю функцію.
Постараюся показати приклад, де це може бути корисним.
Наприклад, в цьому штучному прімере_ який явно вимагає рефакторінга, є розгалуження, коли важку функцію потрібно викликати кілька разів, коли тільки один раз, а коли її зовсім не потрібно викликати.
int calc(int cases, int cases_2, int base) { int result = 0; switch (cases) { case 0: { result = 3 * heavy_calc(base); break; } case 1: { result = 4; break; } case 2: { result = 2 * heavy_calc(base); break; } case 3: { result = 3 * heavy_calc(base); if (cases_2 < 2) { result += heavy_calc(base) / 2; } break; } default: return heavy_calc(base); } switch (cases_2) { case 0: { result = result * heavy_calc(base); break; } case 1: { result += result; break; } case 2: { return result - 1; } default: return 2 * heavy_calc(base); } return result; }
Одним з можливих рішень буде викликати цю функцію один раз на самому початку і зберігати результат в змінну, але тоді ми мовчимо виконання коду в тих гілках, де не потрібно виклик важкої функції. Тому варто подумати над тим, як переписати код так, щоб важка функція викликалася всього один раз.
Лінивий шаблонний функтор з кешуванням результату
Для цього пропоную написати шаблонний клас функтора.
УВАГА!!! Даний код працює тільки при використанні стандарту C++17 або новіше
#ifndef LAZYCACHEDFUNCTION_H #define LAZYCACHEDFUNCTION_H #include <type_traits> // Шаблон, як шаблонного параметра буде приймати тип функції, яку треба викликати // і буде виводити тип значення через std :: result_of_t зі стандарту C ++ 17 template <typename T, typename CachedType = typename std::result_of_t<T()>> class LazyCachedFunction { public: // Конструктор функтора приймає в якості аргументу функцію, яку потрібно викликати // і переміщує її в поле m_function template<typename T> explicit inline LazyCachedFunction(T&& function) : m_function(std::forward<T>(function)) { } // Робимо перевантаження оператора круглих дужок, // щоб виклик функції всередині функтора був схожий на виклик звичайної функції auto operator()() const { // Перевіряємо, що кеш є if (!m_initialized) { // якщо немає, то викликаємо важку функцію m_value = m_function(); m_initialized = true; } // Повертаємо результат обчислень з кешу return m_value; } private: T m_function; ///< викликається функція mutable CachedType m_value; ///< кешированний результат mutable bool m_initialized {false}; ///< прапор ініціалізації, який повідомляє, що кеш заповнений }; #endif // LAZYCACHEDFUNCTION_H
Приклад
А тепер використовуємо даний функтор
#include <iostream> #include <string> #include "lazycachedfunction.h" using namespace std; int heavy_calc(int base) { cout << "heavy_calc" << std::endl; // sleep(+100500 years) return base * 25; } void calclulateValue(int base) { // Обертаємо важку функцію я лямбда функцію // це найбільш зручний універсальний варіант auto foo = [&]() { return heavy_calc(base); }; // Нам потрібно передати в шаблонний параметр тип функції, тому потрібно використовувати decltype LazyCachedFunction<decltype(foo)> lazyCall(foo); // Далі викликаємо lazyCall() двічі і побачимо, що важка функція викликається за все один раз int fooFoo = lazyCall() + lazyCall(); cout << fooFoo << std::endl; } int main() { // Зробимо подвійну перевірку подвійним викликом функції calclulateValue calclulateValue(1); calclulateValue(10); return 0; }
Висновок в консоль
heavy_calc 50 heavy_calc 500
Перевірка, що функція не викликається в разі, якщо ми не викликаємо оператор круглий дужок у функтора
Для такої перевірки можемо змінити функцію calclulateValue наступним чином
void calclulateValue(int base) { auto foo = [&]() { return heavy_calc(base); }; LazyCachedFunction<decltype(foo)> lazyCall(foo); int fooFoo = 10; cout << fooFoo << std::endl; }
Висновок в консоль буде наступним
10 10
Що доводить, що функція не викликається тоді, коли це не потрібно.
Відповідно, в разі, коли не завжди потрібно виконувати деякі важкі обчислення, подібний функтор може дещо оптимізувати роботу програми.
Не сильно вплинувши на структуру методу, який був написаний раніше, а також дозволить зберегти читаність коду.