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.