Um die Idee zu entwickeln, das Ergebnis schwerer Funktionsberechnungen zwischenzuspeichern , schlage ich vor, eine kleine Vorlagenklasse zu schreiben, die eine Funktion als Argument nimmt, nämlich eine Lambda-Funktion als das universellste Werkzeug, in dem eine schwere Funktion ausgeführt wird.
In diesem Fall wird das Ergebnis der Funktion nicht zum Zeitpunkt der Erstellung des Functors berechnet, sondern in dem Moment, in dem die Operatorklammer () aufgerufen wird. Und das Ergebnis wird zwischengespeichert. Dadurch können Sie eine schwere Funktion nicht mehr als einmal während der Ausführung einer Methode aufrufen.
Einführung
Natürlich können Sie zuerst eine schwere Funktion ausführen und das Ergebnis in einer Variablen speichern, aber wenn die Logik Ihres Programms es nicht erfordert, dass Sie den gewünschten Wert sofort berechnen, sondern nur, wenn Sie ihn wirklich brauchen. Dann muss diese Funktion überhaupt nicht aufgerufen werden.
Ich werde versuchen, ein Beispiel zu zeigen, wo dies nützlich sein kann.
Zum Beispiel gibt es in diesem künstlichen Beispiel_, das eindeutig ein Refactoring erfordert, Verzweigungen, wenn eine schwere Funktion mehrmals aufgerufen werden muss, wenn sie nur einmal aufgerufen werden muss, und wenn sie überhaupt nicht aufgerufen werden muss.
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; }
Eine mögliche Lösung wäre, diese Funktion einmal ganz am Anfang aufzurufen und das Ergebnis in einer Variablen zu speichern, aber dann werden wir die Ausführung des Codes in den Zweigen verlangsamen, in denen kein schwerer Funktionsaufruf erforderlich ist. Daher lohnt es sich, darüber nachzudenken, wie man den Code so umschreibt, dass eine schwere Funktion nur einmal aufgerufen wird.
Lazy Template-Funktor mit Ergebnis-Caching
Dazu schlage ich vor, eine Funktor-Template-Klasse zu schreiben.
BEACHTUNG!!! Dieser Code funktioniert nur, wenn Standard-C++17 oder neuer verwendet wird
#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
Beispiel
Und jetzt verwenden wir diesen Funktor
#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; }
Konsolenausgabe
heavy_calc 50 heavy_calc 500
Überprüfen Sie, dass die Funktion nicht aufgerufen wird, wenn wir den Klammeroperator im Funktor nicht aufrufen
Für eine solche Überprüfung können wir die Funktion computeValue wie folgt ändern
void calclulateValue(int base) { auto foo = [&]() { return heavy_calc(base); }; LazyCachedFunction<decltype(foo)> lazyCall(foo); int fooFoo = 10; cout << fooFoo << std::endl; }
Die Ausgabe an die Konsole sieht wie folgt aus
10 10
Was beweist, dass die Funktion nicht aufgerufen wird, wenn sie nicht benötigt wird.
Dementsprechend kann ein solcher Funktor in dem Fall, in dem es nicht immer notwendig ist, einige schwere Berechnungen durchzuführen, das Programm etwas optimieren.
Ohne die Struktur der zuvor geschriebenen Methode stark zu beeinflussen, trägt es auch dazu bei, den Code lesbar zu halten.