Evgenii Legotckoi
Evgenii Legotckoi22 августа 2019 г. 3:42

C++17 - Ленивый шаблонный функтор с кэшированием результата вычислений тяжёлой функции

Развивая идею кэширования результата вычислений тяжелых функций , предлагаю написать небольшой класс-шаблон, который будет принимать в качестве аргумента функцию, а именно лямбда-функция, как наиболее универсальный инструмент, в рамках которого будет выполняться тяжелая функция.

В этом случае результат работы функции будет вычисляться не в момент создания Функтора, а в момент вызова операторной скобки () . И при этом результат будет кэшироваться. Это позволит не вызывать тяжелую функцию более одного раза в рамках выполнения метода.


Вступление

Конечно, можно сначала выполнить тяжелую функцию и сохранить результат в переменную, но если логика вашей программы не требует сразу вычислять нужное значение, а только если оно действительно нужно. Тогда вообще нет необходимости вызывать эту функцию.

Я постараюсь показать пример, где это может быть полезно.

Например, в этом искусственном example_, который явно требует рефакторинга, есть ветки, когда тяжелую функцию нужно вызывать несколько раз, когда только один раз, а когда вообще не нужно вызывать.

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>

// The template, as a template parameter, will take the type of function to be called
// and will print the return type via std :: result_of_t from the C++17 standard
template <typename T, typename CachedType = typename std::result_of_t<T()>>
class LazyCachedFunction
{
public:
    // The constructor of the functor takes as argument the function to be called
    // and move it to the m_function member
    template<typename T>
    explicit inline LazyCachedFunction(T&& function) :
        m_function(std::forward<T>(function))
    {
    }

    // Do the operator overload of parentheses,
    // so that calling a function inside a functor is like calling a regular function
    auto operator()() const
    {
        // Check that the cache exists
        if (!m_initialized)
        {
            // if not, then call the heavy function
            m_value = m_function();
            m_initialized = true;
        }
        // We return the result of calculations from the cache
        return m_value;
    }

private:
    T m_function;                       ///< function called
    mutable CachedType m_value;         ///< cached result
    mutable bool m_initialized {false}; ///< initialization flag, which indicates that the cache is full
};

#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)
{
    // Wrap heavy function i lambda function
    // this is the most convenient universal option
    auto foo = [&]()
    {
        return heavy_calc(base);
    };
    // We need to pass the function type to the template parameter, so we need to use decltype
    LazyCachedFunction<decltype(foo)> lazyCall(foo);
    // Next, call lazyCall() twice and see that the heavy function is called only once
    int fooFoo = lazyCall() + lazyCall();
    cout << fooFoo << std::endl;
}

int main()
{
    // Let's double check by double calling 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

Что доказывает, что функция не вызывается, когда она не требуется.

Соответственно, в случае, когда не всегда нужно выполнять какие-то тяжелые вычисления, такой функтор может несколько оптимизировать программу.
Не сильно затрагивая структуру метода, который был написан ранее, он также поможет сохранить читабельность кода.

Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

Вам это нравится? Поделитесь в социальных сетях!

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь
AD

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:50баллов,
  • Очки рейтинга-4
m
  • molni99
  • 26 октября 2024 г. 1:37

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:80баллов,
  • Очки рейтинга4
m
  • molni99
  • 26 октября 2024 г. 1:29

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:20баллов,
  • Очки рейтинга-10
Последние комментарии
ИМ
Игорь Максимов22 ноября 2024 г. 11:51
Django - Урок 017. Кастомизированная страница авторизации на Django Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
Evgenii Legotckoi
Evgenii Legotckoi31 октября 2024 г. 14:37
Django - Урок 064. Как написать расширение для Python Markdown Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup
A
ALO1ZE19 октября 2024 г. 8:19
Читалка fb3-файлов на Qt Creator Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
ИМ
Игорь Максимов5 октября 2024 г. 7:51
Django - Урок 064. Как написать расширение для Python Markdown Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas55 июля 2024 г. 11:02
QML - Урок 016. База данных SQLite и работа с ней в QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
Сейчас обсуждают на форуме
Evgenii Legotckoi
Evgenii Legotckoi24 июня 2024 г. 15:11
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
t
tonypeachey115 ноября 2024 г. 6:04
google domain [url=https://google.com/]domain[/url] domain [http://www.example.com link title]
NSProject
NSProject4 июня 2022 г. 3:49
Всё ещё разбираюсь с кешем. В следствии прочтения данной статьи. Я принял для себя решение сделать кеширование свойств менеджера модели LikeDislike. И так как установка evileg_core для меня не была возможна, ибо он писался…
9
9Anonim25 октября 2024 г. 9:10
Машина тьюринга // Начальное состояние 0 0, ,<,1 // Переход в состояние 1 при пустом символе 0,0,>,0 // Остаемся в состоянии 0, двигаясь вправо при встрече 0 0,1,>…

Следите за нами в социальных сетях