Evgenii Legotckoi
Aug. 22, 2019, 1:42 p.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.

  1. int calc(int cases, int cases_2, int base)
  2. {
  3. int result = 0;
  4.  
  5. switch (cases)
  6. {
  7. case 0:
  8. {
  9. result = 3 * heavy_calc(base);
  10. break;
  11. }
  12. case 1:
  13. {
  14. result = 4;
  15. break;
  16. }
  17. case 2:
  18. {
  19. result = 2 * heavy_calc(base);
  20. break;
  21. }
  22. case 3:
  23. {
  24. result = 3 * heavy_calc(base);
  25. if (cases_2 < 2)
  26. {
  27. result += heavy_calc(base) / 2;
  28. }
  29. break;
  30. }
  31. default:
  32. return heavy_calc(base);
  33. }
  34.  
  35. switch (cases_2)
  36. {
  37. case 0:
  38. {
  39. result = result * heavy_calc(base);
  40. break;
  41. }
  42. case 1:
  43. {
  44. result += result;
  45. break;
  46. }
  47. case 2:
  48. {
  49. return result - 1;
  50. }
  51. default:
  52. return 2 * heavy_calc(base);
  53. }
  54. return result;
  55. }

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

  1. #ifndef LAZYCACHEDFUNCTION_H
  2. #define LAZYCACHEDFUNCTION_H
  3.  
  4. #include <type_traits>
  5.  
  6. // The template, as a template parameter, will take the type of function to be called
  7. // and will print the return type via std :: result_of_t from the C++17 standard
  8. template <typename T, typename CachedType = typename std::result_of_t<T()>>
  9. class LazyCachedFunction
  10. {
  11. public:
  12. // The constructor of the functor takes as argument the function to be called
  13.     // and move it to the m_function member
  14. template<typename T>
  15. explicit inline LazyCachedFunction(T&& function) :
  16. m_function(std::forward<T>(function))
  17. {
  18. }
  19.  
  20. // Do the operator overload of parentheses,
  21.     // so that calling a function inside a functor is like calling a regular function
  22. auto operator()() const
  23. {
  24. // Check that the cache exists
  25. if (!m_initialized)
  26. {
  27. // if not, then call the heavy function
  28. m_value = m_function();
  29. m_initialized = true;
  30. }
  31. // We return the result of calculations from the cache
  32. return m_value;
  33. }
  34.  
  35. private:
  36. T m_function; ///< function called
  37. mutable CachedType m_value; ///< cached result
  38. mutable bool m_initialized {false}; ///< initialization flag, which indicates that the cache is full
  39. };
  40.  
  41. #endif // LAZYCACHEDFUNCTION_H
  42.  

Example

And now we use this functor

  1. #include <iostream>
  2. #include <string>
  3.  
  4. #include "lazycachedfunction.h"
  5.  
  6. using namespace std;
  7.  
  8. int heavy_calc(int base)
  9. {
  10. cout << "heavy_calc" << std::endl;
  11. // sleep(+100500 years)
  12. return base * 25;
  13. }
  14.  
  15. void calclulateValue(int base)
  16. {
  17. // Wrap heavy function i lambda function
  18.     // this is the most convenient universal option
  19. auto foo = [&]()
  20. {
  21. return heavy_calc(base);
  22. };
  23. // We need to pass the function type to the template parameter, so we need to use decltype
  24. LazyCachedFunction<decltype(foo)> lazyCall(foo);
  25. // Next, call lazyCall() twice and see that the heavy function is called only once
  26. int fooFoo = lazyCall() + lazyCall();
  27. cout << fooFoo << std::endl;
  28. }
  29.  
  30. int main()
  31. {
  32. // Let's double check by double calling calclulateValue
  33. calclulateValue(1);
  34. calclulateValue(10);
  35. return 0;
  36. }
  37.  

Console Output

  1. heavy_calc
  2. 50
  3. heavy_calc
  4. 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

  1. void calclulateValue(int base)
  2. {
  3. auto foo = [&]()
  4. {
  5. return heavy_calc(base);
  6. };
  7. LazyCachedFunction<decltype(foo)> lazyCall(foo);
  8. int fooFoo = 10;
  9. cout << fooFoo << std::endl;
  10. }

The output to the console will be as follows

  1. 10
  2. 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.

Do you like it? Share on social networks!

Comments

Only authorized users can post comments.
Please, Log in or Sign up
  • Last comments
  • Evgenii Legotckoi
    March 9, 2025, 9:02 p.m.
    К сожалению, я этого подсказать не могу, поскольку у меня нет необходимости в обходе блокировок и т.д. Поэтому я и не задавался решением этой проблемы. Ну выглядит так, что вам действитель…
  • VP
    March 9, 2025, 4:14 p.m.
    Здравствуйте! Я устанавливал Qt6 из исходников а также Qt Creator по отдельности. Все компоненты, связанные с разработкой для Android, установлены. Кроме одного... Когда пытаюсь скомпилиров…
  • ИМ
    Nov. 22, 2024, 9:51 p.m.
    Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
  • Evgenii Legotckoi
    Oct. 31, 2024, 11:37 p.m.
    Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup
  • A
    Oct. 19, 2024, 5:19 p.m.
    Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html