【问题标题】:Clean way to lazy initialize and cache internal value in lambda在 lambda 中延迟初始化和缓存内部值的干净方法
【发布时间】:2019-01-18 09:48:46
【问题描述】:

让代码先用幼稚的方法自己说话:

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();
}

我想在第一次调用时计算 lambda 内部缓存值。一种天真的方法是使用static 缓存,但它是increases binary size and refuses to be be inlined

我想出了在捕获列表中创建 cache 并将 lambda 标记为 mutable,什么是 inlines without problems,但需要缓存以默认值开始,这可能会破坏类不变量。


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

我的第三种方法使用boost::optional in mutable lambda

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

它可以正常工作,但将我视为一种捕获列表 + mutable 关键字黑客。此外,mutable 会影响所有捕获的参数,因此会降低 lambda 在实际使用中的安全性。

也许对此有更好/更干净的解决方案?或者只是不同的方法最终会产生相同的效果。

编辑,一些背景: 选择 Lambda 方法是因为我正在修改一些回调 lambda,目前用作: [this, param]{this-&gt;onEvent(heavy_calc(param));} 我想减少 heavy_calc 调用而不提前评估它(仅在第一次调用时)

【问题讨论】:

  • heavy_calc 应该在 lambda 内部调用一次,还是可以在外部调用? (出于线程安全原因提出问题)
  • 我想只在内部调用它。我没有考虑线程安全,但是另一个具有线程安全的天真解决方案是thread_local 而不是static,对吧?
  • 从线程安全的角度来看,您的静态答案是可以的。据我所知,在 C++11 中,可以保证静态变量的线程安全初始化。另一种选择是使用 call_once en.cppreference.com/w/cpp/thread/call_once 但我会使用静态。
  • 所以 thread_local 和 static 都可以。我关于线程安全的问题与应该调用什么线程有关。你回答说应该从调用第一个 lambda 函数的线程中调用它。

标签: c++ lambda c++14


【解决方案1】:

说实话,我看不出有什么理由在这里使用 lambda。您可以编写一个常规的可重用类来缓存计算值。如果您坚持使用 lambda,那么您可以将值计算移至参数,这样就不需要做任何事情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

使用一些模板可以编写一个懒惰评估和缓存任意计算结果的类:

#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

【讨论】:

  • 我认为这个想法是在不需要时不要调用heavy_calc(),例如,如果您有一些可能不使用foo 的分支。否则我完全同意可重用缓存类。
  • @GPhilo 构造 lambda 时
  • @VTT 所以在你的return 中你会调用heavy_calc 两次,因为foo 是双倍的?或者当foo 被定义然后被foooperator() 隐式调用时,lambda 是否只构造一次? (另外,foo到底是什么?一个无名的类?某种函数?我以前从来没有想到过auto这样的用法)
  • @GPhilo foo 是匿名可调用类的一个实例,heavy_calc 只会在foo 构造时被调用一次。
  • @GPhilo:那你需要看看大括号初始化
【解决方案2】:

它可以正常工作,但在我看来是一种捕获列表 + 可变关键字破解。可变也会影响所有捕获的参数,因此在实际使用中会降低 lambda 的安全性。

有解决方案来滚动你自己的手工制作的 lambda:

#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();
}

它以相同的方式内联,您无需依赖捕获+可变 hack。

【讨论】:

    【解决方案3】:

    我确实相信这正是可变 lambda 的用例。如果您不想让所有变量都可变,我建议只使用一个 mutable 字段创建仿函数类。这样你就可以两全其美了(好吧,这不是那么简洁)。额外的好处是operator()const(这是完全正确的,因为它总是返回相同的值)

    #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();
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-07-12
      • 2012-10-11
      • 1970-01-01
      • 1970-01-01
      • 2020-05-10
      • 2010-11-30
      相关资源
      最近更新 更多