Evgenii Legotckoi
Evgenii LegotckoiAug. 22, 2019, 3:42 a.m.

C ++ 17 - Lazy template functor with caching heavy function computation result

Developing the idea of caching the result of calculations of heavy functions , I propose to write a small template class, which will take the function as an argument, namely the lambda function, as the most universal an instrument within which a heavy function will be performed.

In this case, the result of the function will not be calculated at the time of creation of the Functor, but at the time of calling the operator parentheses () . And at the same time, the result will be cached. That will allow not to call a heavy function more than once as part of the execution of a method.


Introduction

Of course, you can first perform a heavy function and save the result in a variable, but if the logic of your program does not need to immediately calculate the desired value, but only if it is really needed. Then there is no need to call this function at all.

I will try to show an example where this can be useful.

For example, in this artificial example_, which clearly requires refactoring, there are branches when a heavy function needs to be called several times, when only once, and when it does not need to be called at all.

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;
}

One of the possible solutions would be to call this function once at the very beginning and save the result in a variable, but then we will slow down the code execution in those branches where the heavy function call is not required. Therefore, it is worth considering how to rewrite the code so that the heavy function is called only once.

Lazy template functor with result caching

To do this, I propose to write a functor template class.

ATTENTION!!! This code only works when using the standard C ++ 17 or later

#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

Example

And now we use this functor

#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;
}

Console Output

heavy_calc
50
heavy_calc
500

Check that the function is not called if we do not call the parenthesis operator on the functor

For such a check, we can change the calclulateValue function as follows

void calclulateValue(int base)
{
    auto foo = [&]()
    {
        return heavy_calc(base);
    };
    LazyCachedFunction<decltype(foo)> lazyCall(foo);
    int fooFoo = 10;
    cout << fooFoo << std::endl;
}

The output to the console will be as follows

10
10

Which proves that the function is not called when it is not required.

Accordingly, in the case when it is not always necessary to perform some heavy calculations, such a functor can somewhat optimize the program.
Without greatly affecting the structure of the method that was written earlier, it will also help to keep the code readable.

We recommend hosting TIMEWEB
We recommend hosting TIMEWEB
Stable hosting, on which the social network EVILEG is located. For projects on Django we recommend VDS hosting.

Do you like it? Share on social networks!

Comments

Only authorized users can post comments.
Please, Log in or Sign up
L
  • Leo
  • Sept. 26, 2023, 11:43 a.m.

C++ - Test 002. Constants

  • Result:41points,
  • Rating points-8
L
  • Leo
  • Sept. 26, 2023, 11:32 a.m.

C++ - Test 001. The first program and data types

  • Result:93points,
  • Rating points8
Last comments
IscanderChe
IscanderCheSept. 13, 2023, 9:11 a.m.
QScintilla C++ example По горячим следам (с другого форума вопрос задали, пришлось в памяти освежить всё) решил дополнить. Качаем исходники с https://riverbankcomputing.com/software/qscintilla/downlo…
Evgenii Legotckoi
Evgenii LegotckoiSept. 6, 2023, 7:18 a.m.
Qt/C++ - Lesson 048. QThread — How to work with threads using moveToThread Разве могут взаимодействовать объекты из разных нитей как-то, кроме как через сигнал-слоты?" Могут. Выполняя оператор new , Вы выделяете под объект память в куче (heap), …
AC
Andrei CherniaevSept. 5, 2023, 3:37 a.m.
Qt/C++ - Lesson 048. QThread — How to work with threads using moveToThread Я поясню свой вопрос. Выше я писал "Почему же в методе MainWindow::on_write_1_clicked() Можно обращаться к методам exampleObject_1? Разве могут взаимодействовать объекты из разных…
n
nvnAug. 31, 2023, 9:47 a.m.
QML - Lesson 004. Signals and Slots in Qt QML Здравствуйте! Прекрасный сайт, отличные статьи. Не хватает только готовых проектов для скачивания. Многих комментариев типа appCore != AppCore просто бы не было )))
NSProject
NSProjectAug. 24, 2023, 1:40 p.m.
Django - Tutorial 023. Like Dislike system using GenericForeignKey Ваша ошибка связана с gettext from django.utils.translation import gettext_lazy as _ Поле должно выглядеть так vote = models.SmallIntegerField(verbose_name=_("Голос"), choices=VOTES) …
Now discuss on the forum
IscanderChe
IscanderCheSept. 17, 2023, 9:24 a.m.
Интернационализация строк в QMessageBox Странная картина... Сделал минимально работающий пример - всё работает. Попробую на другой операционке. Может, дело в этом.
NSProject
NSProjectSept. 17, 2023, 8:49 a.m.
Помогите добавить Ajax в проект В принципе ничего сложного с отправкой на сервер нет. Всё что ты хочешь отобразить на странице передаётся в шаблон и рендерится. Ты просто создаёшь файл forms.py в нём описываешь свою форму и в …
BlinCT
BlinCTSept. 15, 2023, 12:35 p.m.
Размеры полей в TreeView Всем привет. Пытаюсь сделать дерево вот такого вида Пытаюсь организовать делегат для каждой строки в дереве. ТО есть отступ какого то размера и если при открытии есть под…
IscanderChe
IscanderCheSept. 8, 2023, 12:07 p.m.
Кастомная QAbstractListModel и цвет фона, цвет текста и шрифт Похоже надо не абстрактный , а "реальный" типа QSqlTableModel Да, но не совсем. Решилось с помощью стайлшитов и setFont. Спасибо за отлик!
Evgenii Legotckoi
Evgenii LegotckoiSept. 6, 2023, 6:35 a.m.
Вопрос: Нужно ли в деструкторе удалять динамически созданные QT-объекты. Напр: Зависит от того, как эти объекты были созданы. Если вы передаёте указатель на parent объект, то не нужно, Ядро Qt само разрулит удаление, если нет, то нужно удалять вручную, иначе будет ут…

Follow us in social networks