Evgenii Legotckoi
Evgenii Legotckoi22 августа 2019 г. 13: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 хостинг.

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

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь
1
  • 12333
  • 18 июля 2024 г. 15:34

Qt - Тест 001. Сигналы и слоты

  • Результат:63баллов,
  • Очки рейтинга-1
1
  • 12333
  • 18 июля 2024 г. 15:25

C++ - Тест 005. Структуры и Классы

  • Результат:50баллов,
  • Очки рейтинга-4
AM

C++ - Тест 005. Структуры и Классы

  • Результат:33баллов,
  • Очки рейтинга-10
Последние комментарии
d
dblas55 июля 2024 г. 21:02
QML - Урок 016. База данных SQLite и работа с ней в QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
k
kmssr9 февраля 2024 г. 5:43
Qt Linux - Урок 001. Автозапуск Qt приложения под Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий Кононенко5 февраля 2024 г. 12:50
Qt WinAPI - Урок 007. Работаем с ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVA25 декабря 2023 г. 21:30
Boost - статическая линковка в CMake проекте под Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
J
JonnyJo25 декабря 2023 г. 19:38
Boost - статическая линковка в CMake проекте под Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
Сейчас обсуждают на форуме
F
Fynjy22 июля 2024 г. 14:15
при создании qml проекта Kits есть но недоступны для выбора Поставил Qt Creator 11.0.2. Qt 6.4.3 При создании проекта Qml не могу выбрать Kits, они все недоступны, хотя настроены и при создании обычного Qt Widget приложения их можно выбрать. В чем может …
BlinCT
BlinCT25 июня 2024 г. 11:00
Нарисовать кривую в qml Всем привет. Имеется Лист листов с тосками, точки получаны интерполяцией Лагранжа. Вопрос, как этими точками нарисовать кривую? ChartView отпадает сразу, в qt6.7 появился новый элемент…
Evgenii Legotckoi
Evgenii Legotckoi25 июня 2024 г. 1:11
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
BlinCT
BlinCT5 мая 2024 г. 15:46
Написать свой GraphsView Всем привет. В Qt есть давольно старый обьект дял работы с графиками ChartsView и есть в 6.7 новый но очень сырой и со слабым функционалом GraphsView. По этой причине я хочу написать х…
Evgenii Legotckoi
Evgenii Legotckoi3 мая 2024 г. 0:07
Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Добрый день. По моему мнению - да, но то, что будет касаться вызовов к функционалу Андроида, может создать огромные трудности.

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