【问题标题】:Is it possible to avoid copying lambda functor in this situation?在这种情况下是否可以避免复制 lambda 仿函数?
【发布时间】:2012-01-18 01:07:48
【问题描述】:

我在 C++11 中使用 lambda 制作了一个 finally 模拟器,如下所示:

#include <cstdio>

template<typename Functor>
struct Finalizer
{
    Finalizer(Functor& func) : func_(func) {} // (1)
    ~Finalizer() { func_(); }

private:
    Functor func_; // (2)
};

template<typename functor>
Finalizer<functor> finally(functor& func)
{
    return Finalizer<functor>(func); (3)
}

int main()
{
    int a = 20;

    // print the value of a at the escape of the scope
    auto finalizer = finally([&]{ printf("%d\n", a); }); // (4)
}

代码按预期工作,但在 Finalizer 结构 (1) 的 ctor 处存在不希望的复制 ctor 调用(lambda 仿函数)。 (幸运的是,RVO 避免了 finally 函数 (3 -> 4) 中 return 语句的复制构造。)

编译器不会消除复制 ctor 调用(至少在 vc10 中 - gcc 可能会对其进行优化),并且如果 Finalizer 结构 (2) 中的仿函数类型更改为引用它将崩溃,因为 finally 调用 (4) 中的 lambda 参数是 r 值。

当然可以像下面这样“优化”代码

template<typename Functor>
struct Finalizer
{
    Finalizer(Functor& func) : func_(func) {}
    ~Finalizer() { func_(); }

private:
    Functor& func_;
};

int main()
{
    int a = 20;

    auto finalizer = [&]{ printf("%d\n", a); };
    Finalizer<decltype(finalizer)> fin(finalizer);
}

没有开销,只有一个 printf 调用被放置在作用域的末尾。但是……我不喜欢。 :( 我尝试用宏包装它,但它需要声明两个“名称” - 一个用于 lambda 对象,另一个用于终结器对象。

我的目标很简单 -

  1. 应该消除所有可以避免的不必要的性能开销。理想情况下,不应该有函数调用,每个过程都应该内联。
  2. 保持简洁的表达式作为其效用函数的目的。允许使用宏,但不鼓励使用。

有什么办法可以避免这种情况吗?

【问题讨论】:

  • 至少说template&lt;typename functor&gt; Finalizer&lt;functor&gt; finally(functor&amp;&amp; func) { return Finalizer&lt;typename std::remove_reference&lt;functor&gt;::type&gt;(std::forward&lt;functor&gt;(func)); }
  • 如果你需要复制 lambda 因为它是类的成员,那么你需要复制它,故事结束。但问题是什么 - 一个 ref-capturing lambda 充其量将包含一些复制成本不高的引用。
  • 这就是 C++ 右值引用的用途。
  • 感谢您的每一个回答。 (对不起我的反应迟钝-我的英语很短:()我尝试使用 R 值,它不能解决问题。我认为 Kerrek SB 是正确的-问题的根源在于 lambda 对象本身是实例化为临时对象。可能需要在编译时模拟正常顺序评估之类的东西来解决这个问题。(我什至不知道这是否可能)
  • 不建议使用引用成员,因为它所绑定的对象的生命周期永远不会延长,并且一旦构造函数运行完毕,引用就会悬空。

标签: c++ lambda c++11 finally


【解决方案1】:

我认为 lambda 有移动构造函数?如果是这样,并且如果您只在finally 中使用右值,那么&amp;&amp;forward 将移动而不是复制。

#include <cstdio>

template<typename Functor>
struct Finalizer
{
    Finalizer(Functor&& func) : func_(std::move(func)) {}
    Finalizer(Functor const& func) : func_(func) {} // (1)
    ~Finalizer() { func_(); }

private:
    Functor func_; // (2)
};

template<typename functor>
Finalizer<std::remove_reference<functor>::type> finally(functor&& func)
{
    return Finalizer<std::remove_reference<functor>::type>(std::forward<functor>(func)); // (3)
}

int main()
{
    int a = 20;

    // print the value of a at the escape of the scope
    auto finalizer = finally([&]{ printf("%d\n", a); }); // (4)
}

应该可以纠正一些更智能的东西,它也可以与左值一起正常工作,这样你的“优化”版本就可以编译并在它不能移动时复制。在这种情况下,我建议您使用 Functor&lt;std::remove_reference&lt;functor&gt;::type&gt; 之类的东西来确保 Functor 是正确的类型,无论参数是由 &amp; 还是 &amp;&amp; 或其他什么传递。

【讨论】:

  • 我的这个答案还是有点破。我会把它做成社区维基。我写正确的代码已经太晚了:-)
  • 这个解决方案的问题是,如果没有发生省略,两个函子可能最终被调用:一旦调用finally(在statement) 运行,并在finalizer 被销毁时在范围结束时运行一次。问题中也存在这个问题(没有参考的版本)。
  • @LucDanton,好点子。我当时没有想到这一点。传递给 to finally 的临时变量是函子类型,它不是 Finalizer 类型。因此,不会尝试执行它。我们不在乎有多少仿函数被创建和销毁。我们只关心创建和销毁了多少Finalizer 对象——这就是问题所在。从finally 返回的临时值是Finalizer 类型。如果幸运的话,编译器会优化并使用 RVO 等来保证会创建一个 Finalizer。 auto_ptr&lt;Finalizer&gt; 也许?
  • @LucDanton,(假设 C++11),也许另一种解决方案是使 Finalizer 不可复制且只能移动。并安排移动构造函数以确保原始对象被标记为不运行仿函数? if (still_a_real_object) this-&gt;func_();
  • 确实如此。我个人的解决方案(因为我有一个类似finally 的实用程序)是拥有一个跟踪对象状态的bool 成员,并让移动构造函数在窃取其参数时更新该成员。
【解决方案2】:

也许接受构造函数的函子参数作为右值引用?

【讨论】:

    【解决方案3】:

    我只允许functions 按值传递。 这是在整个 STL 算法中完成的。

    那么,两种调用方式都可以:

    • 这个
        auto finalizer = finally([&]{ printf("%d\n", a); }); // this can be dangerous, if passed by reference to finally.
    
    • 还有这个
        auto finalizer = [&]{ printf("%d\n", a); };
        Finalizer<decltype(finalizer)> fin(finalizer);
    

    这样做的原因是functions 不应该很大。 我认为 STL 算法在传递functions 时遵循相同的推理。

    这是完整的代码

    #include <cstdio>
    
    template<typename Functor>
    struct Finalizer
    {
        Finalizer(Functor func) : func_(func) {} /// pass by value
        ~Finalizer() { func_(); }
    
    private:
        Functor func_; // 
    };
    
    template<typename functor>
    Finalizer<functor> finally(functor func)  /// pass by value
    {
        return Finalizer<functor>(func); 
    }
    
    int main()
    {
        int a = 20;
    
        // print the value of a at the escape of the scope
        auto finalizer = finally([&]{ printf("%d\n", a); }); 
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-07-17
      • 1970-01-01
      • 2023-03-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-24
      • 1970-01-01
      相关资源
      最近更新 更多