Lassen Sie den Code zuerst mit naivem Ansatz für sich selbst sprechen:

int heavy_calc() // needed to be called once
{
    // sleep(7500000 years)
    return 42;
}

int main()
{
    auto foo = [] {
        // And cached for lambda return value
        static int cache = heavy_calc();
        return cache;
    };
    return foo() + foo();
}

Ich möchte, dass der interne zwischengespeicherte Lambda-Wert beim ersten Aufruf berechnet wird. Ein naiver Ansatz besteht darin, den static - Cache zu verwenden, der jedoch die Binärgröße erhöht und sich weigert, eingebunden zu werden .

Ich habe mir ausgedacht, cache in der Erfassungsliste zu erstellen und Lambda als mutable zu markieren, was problemlos inline ist, erfordert jedoch, dass der Cache mit dem Standardwert beginnt, wodurch die Klasseninvariante möglicherweise unterbrochen wird.


auto foo = [cache=0] () mutable {
    // And cached for lambda return value
    if(!cache)
        cache = heavy_calc();
    return cache;
};

Mein dritter Ansatz verwendet boost::optional in veränderlichem lambda

auto foo = [cache=std::optional<int>{}] () mutable {
    // And cached for lambda return value
    if(!cache)
        cache = heavy_calc();
    return *cache;
};

Es funktioniert einwandfrei, sieht aber für mich als eine Art Erfassungsliste + mutable Keyword-Hack aus. Außerdem wirkt sich mutable auf alle erfassten Parameter aus, sodass Lambda im realen Gebrauch weniger sicher ist.

Vielleicht gibt es dafür eine bessere / sauberere Lösung? Oder einfach ein anderer Ansatz, der den gleichen Effekt hat.

EDIT, einige Hintergrundinformationen: Der Lambda-Ansatz wird gewählt, da ich ein Rückruf-Lambda ändere, das derzeit verwendet wird als: [this, param]{this->onEvent(heavy_calc(param));} Ich möchte heavy_calc Anrufe reduzieren, ohne sie vorher auszuwerten (nur beim ersten Anruf).

9
R2RT 18 Jän. 2019 im 12:48

3 Antworten

Beste Antwort

Um ehrlich zu sein, sehe ich hier keinen Grund, Lambda zu verwenden. Sie können eine reguläre wiederverwendbare Klasse schreiben, um den Berechnungswert zwischenzuspeichern. Wenn Sie darauf bestehen, Lambda zu verwenden, können Sie die Wertberechnung in Parameter verschieben, sodass Sie nichts vornehmen müssen mutable:

int heavy_calc() // needed to be called once
{
    // sleep(7500000 years)
    return 42;
}

int main()
{
    auto foo
    {
        [cache = heavy_calc()](void)
        {
            return cache;
        }
    };
    return foo() + foo();
}

Online-Compiler

Mit ein wenig Vorlage ist es möglich, eine Klasse zu schreiben, die das Ergebnis einer beliebigen Berechnung verzögert auswertet und zwischenspeichert:

#include <boost/optional.hpp>
#include <utility>

template<typename x_Action> class
t_LazyCached final
{
    private: x_Action m_action;
    private: ::boost::optional<decltype(::std::declval<x_Action>()())> m_cache;

    public: template<typename xx_Action> explicit
    t_LazyCached(xx_Action && action): m_action{::std::forward<xx_Action>(action)}, m_cache{} {}

    public: auto const &
    operator ()(void)
    {
        if(not m_cache)
        {
            m_cache = m_action();
        }
        return m_cache.value();
    }
};

template<typename x_Action> auto
Make_LazyCached(x_Action && action)
{
    return t_LazyCached<x_Action>{::std::forward<x_Action>(action)};
}

class t_Obj
{
    public: int heavy_calc(int param) // needed to be called once
    {
        // sleep(7500000 years)
        return 42 + param;
    }
};

int main()
{
    t_Obj obj{};
    int param{3};
    auto foo{Make_LazyCached([&](void){ return obj.heavy_calc(param); })};
    return foo() + foo();
}

Online-Compiler

5
user7860670 18 Jän. 2019 im 10:42

Es funktioniert richtig, sieht aber für mich als eine Art Capture-Liste + veränderlichen Keyword-Hack aus. Mutable wirkt sich auch auf alle erfassten Parameter aus, wodurch Lambda im realen Gebrauch weniger sicher ist.

Es gibt die Lösung, um Ihr eigenes handgemachtes Lambda zu rollen:

#include <optional>

int heavy_calc() // needed to be called once
{
    // sleep(7500000 years)
    return 42;
}


int main()
{
    struct {
        std::optional<int> cache;
        int operator()() {
            if (!cache) cache = heavy_calc();
            return *cache;
        }
    } foo;
    return foo() + foo();
}

Es ist genauso eingebunden und Sie müssen sich nicht auf den Capture + Mutable-Hack verlassen.

2
papagaga 18 Jän. 2019 im 10:30

Ich glaube, dass dies genau der Anwendungsfall für veränderliches Lambda ist. Wenn Sie nicht möchten, dass alle Variablen veränderbar sind, empfehle ich, nur eine Funktorklasse mit einem mutable -Feld zu erstellen. Auf diese Weise erhalten Sie das Beste aus beiden Welten (ok, es ist nicht so prägnant). Der zusätzliche Vorteil ist, dass operator() const ist (was ganz richtig ist, da es immer den gleichen Wert zurückgibt).

#include <optional>

int heavy_calc() {
    // sleep(7500000 years)
    return 42;
}
struct my_functor {
    mutable std::optional<int> cache;
    int operator()() const {
        if (!cache) cache = heavy_calc();
        return *cache;
    }
}

int main() {
    my_functor foo;
    return foo() + foo();
}
0
bartop 18 Jän. 2019 im 11:41