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

C++17, lambda, auto, functor

Содержание

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

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

Введение

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

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

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

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

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

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

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

Комментарии

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

Внесите вклад в развитие сообщества EVILEG.

Узнайте, как стать автором сайта.

Изучить
Donate

Добрый день, Дорогие Пользователи !!!

Я Евгений Легоцкой, разработчик EVILEG. И это мой хобби-проект, который помогает учиться программированию другим программистам и разработчикам

Если сайт помог вам, и вы хотите также поддержать развитие сайта, то вы можете сделать пожертвование следующими способами

PayPalYandex.Money
Timeweb

Позвольте мне порекомендовать вам отличный хостинг, на котором расположен EVILEG.

В течение многих лет Timeweb доказывает свою стабильность.

Для проектов на Django рекомендую VDS хостинг

Посмотреть Хостинг Timeweb
6 июня 2020 г. 0:20
Алексей

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

  • Результат:60баллов,
  • Очки рейтинга-1
6 июня 2020 г. 0:15
Алексей

C++ - Тест 001. Первая программа и типы данных

  • Результат:53баллов,
  • Очки рейтинга-4
V
5 июня 2020 г. 17:47
Vladzo

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

  • Результат:83баллов,
  • Очки рейтинга4
Последние комментарии
5 июня 2020 г. 11:52
progammist

Распознавание изображений на Python с помощью TensorFlow и Keras

Огромное спасибо за метериал, по-больше бы подобных статей (с подробным описанием работы и примерами применения) на тему современных технологий. Вопрос поразмышлять. На текущий момент реал…
5 июня 2020 г. 2:39
Евгений Легоцкой

Qt/C++ - Урок 091. Как написать кастомный делегат управляющий подсветкой строки в таблице

По-моему, смысла в этом нет особого. Если делегат будет игнорировать настройки таблицы, то это приведёт ещё к большему непониманию, что вообще происходит, для программиста, который после вас буд…
5 июня 2020 г. 2:34
IscanderChe

Qt/C++ - Урок 091. Как написать кастомный делегат управляющий подсветкой строки в таблице

Сижу, размышляю: можно ли переписать делегата так, чтобы независимо от настроек строк выделялись строки?
5 июня 2020 г. 2:31
Евгений Легоцкой

Qt/C++ - Урок 091. Как написать кастомный делегат управляющий подсветкой строки в таблице

Понятно. Я не обратил внимания на то, что там было в старом коде по настройкам строк :)
Сейчас обсуждают на форуме
s
6 июня 2020 г. 2:54
shuric

Qt/C++ Определение положения курсора над действие(кнопкой) в QToolBar

Доброго дня. Возник вопрос - как можно определить что курсор находится над определенным действием(кнопкой) в qtoolbar ? mainwindow.cpp MainWindow::MainWi…
s
6 июня 2020 г. 1:45
shuric

Qt/C++ особенности QProxyStyle

Да, Вы правы. Код был скопирован с сайта (уже не помню с какого), но решил пойти по пути более легком. Пришлось переписать - кому интересно: использовал stackedWidget для пе…
6 июня 2020 г. 0:08
Алексей

Посоветуйте новичку (базы данных и Qt, что учить)

Блин, а я недавно купил Шлее Qt 5.10 :( С детства хотел стать программистом, баловался Паскалем, писал простенькие программки на Delphi, создавал движок на php, изучал C (забросил и перешел на п…
5 июня 2020 г. 14:09
IscanderChe

QPlainTextEdit настройка цвета фона

Вечер добрый. Пытаюсь настроить цвет фона QPlainTextEdit следующим образом: CodeEditor::CodeEditor(QWidget *parent) : QPlainTextEdit(parent){ ... QPalette::ColorRole role = bac…
5 июня 2020 г. 7:13
IscanderChe

Фильтр для QtableView sql

Добрый день. Для такой фильтрации необходимо использовать QSortFilterProxyModel. В оффдоках есть хороший пример.
О нас
Услуги
© EVILEG 2015-2020
Рекомендует хостинг TIMEWEB