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

C++17, lambda, auto, functor

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.
- company blog
Support the author Donate

Comments

Only authorized users can post comments.
Please, Log in or Sign up
Looking for a Job?
25,000.00 руб. - 30,000.00 руб.
Разработчик Qt/C++
Barnaul, Altai Krai, Russia

For registered users on the site there is a minimum amount of advertising

JuA
Sept. 17, 2019, 8:51 a.m.
Julija Aleksandrova

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

  • Result:33points,
  • Rating points-10
JuA
Sept. 17, 2019, 8:36 a.m.
Julija Aleksandrova

C ++ - Test 004. Pointers, Arrays and Loops

  • Result:10points,
  • Rating points-10
VD
Sept. 16, 2019, 11:47 a.m.
Viktor Dzen'kiv

C++ - Test 002. Constants

  • Result:75points,
  • Rating points2
Last comments
Sept. 17, 2019, 6:07 a.m.
Misha Lebedev

Кстати интересные темы нашёл тут https://emacsway.github.io/ru/django-framework/#django-models Может что полезного тоже Евгений найдёте
Sept. 17, 2019, 4:50 a.m.
Misha Lebedev

Доброго времени суток. Спасибо за хороший ответ, У меня ситуация така что в галлереи будет несколько миллионов фотографий с фильтрами и тегами , и я опасаюсь за производительност . Это ос…
Sept. 17, 2019, 3:23 a.m.
Evgenij Legotskoj

Добрый день. Да, я тоже читал ту статью в своё время и согласен с тем, что внешние ключи гораздо лучше, чем GenericForeignKey. Выборки в ряде случае работают быстрее. Но лично мне про…
Sept. 14, 2019, 5:08 p.m.
Misha Lebedev

Приветствую вас Евгений , давно наблюда за развитием вашего замечательного портала, много полезно тут нашел , переодически зачитываюсь. Теперь по сушеству, делаю портал и там идеально ложи…
Sept. 10, 2019, 4:38 p.m.
Evgenij Legotskoj

function view для модели Article и LikeDislike.LIKE будет выглядеть так def like(request, pk): obj = Article.objects.get(pk=pk) try: likedislike = LikeDislike.objects.get(cont…
Now discuss on the forum
p
Sept. 17, 2019, 5:02 a.m.
pstMem

Да, действительно нужно дебажить, по другому не словить исключение. Уже решил проблему, был выход за предел массива, не правильные входные данные, так что всегда проверяйте размер массива.
Sept. 17, 2019, 3:39 a.m.
Evgenij Legotskoj

Добрый день! На удалённом сервере вряд ли. Этот класс из core модуля, а удалённый сервер - это ещё и network модуль нужно подтягивать. Тут на удалэнном сервере нужно делать программу…
Sept. 17, 2019, 3:30 a.m.
Evgenij Legotskoj

Добрый день! Попробуйте toHex() А также создние QString с помощью from методов. Может быть QString::fromLatin1(). В документации на QString почти два десятка методов from, один из них…
m
Sept. 16, 2019, 1:54 p.m.
mihamuz

Однозначно PostgreSql не ниже 10 ки.
R
Sept. 16, 2019, 7:09 a.m.
RED_Spider

прочитайте https://doc.qt.io/archives/qt-5.11/osx-deployment.html QMAKE_POST_LINK += "~/Qt/5.12.0/clang_64/bin/macdeployqt $${TARGET}.app $$escape_expand( \\n\\t )"
EVILEG
About
Services
© EVILEG 2015-2019
Recommend hosting TIMEWEB