【问题标题】:C++11 Optimization of empty function passed as a callbackC++11优化作为回调传递的空函数
【发布时间】:2013-03-07 13:25:27
【问题描述】:

我有一个函数模板:

template <class ReportFunc>
void func (ReportFunc report_func)
{
    for (/* ... */)
    {
         do_something (a, b);
         report_func (a, b, c);
         do_something_else (b, c);
    }
}

有时需要在没有任何 ReportFunc 的情况下调用 func(),即循环只调用 do_something() 和 do_something_else() 而没有别的。如果我编写了一个不带 ReportFunc 参数的 f() 重载,我将不得不复制 f() 的实现代码,只需删除调用 report_func() 的行。

我有几个此类函数 - 有时我想用 ReportFunc 调用它们,有时不使用它。所以我想避免所有的代码重复。如果我传递一个空的 lambda 或 void 或类似的东西,它是否应该让 C++11 编译器生成一个不调用任何 report_func() 的 f() 实例?它是否与简单地删除调用 report_func() 的行一样快,甚至一个空的 lambda 也会有一些编译器未优化的开销? (在我的具体情况下,我使用 GCC)

另外:如果一个空的 lambda 确实这样做,并且我将函数 f() 的返回类型更改为 ReportFunc,即它返回 report_func 参数,将返回值存储在变量中并调用是否仍然安全它? (即使它是一个空的 lambda?所以理论上可以调用它,它只是意味着什么都没有发生)

【问题讨论】:

  • 向它传递一个带有空内联 operator() 的函数对象。一个普通的空内联函数也应该可以工作。
  • @n.m.如果我传递一个空的 lambda 怎么办,它会一样快吗?我问是因为它使代码看起来更好:lambda 是在使用它的地方定义的,而不是在代码中的其他地方定义的,就像函子一样
  • lambdas 只不过是普通函子的语法糖——它们的性能应该相同。和往常一样,试试看:)
  • 当函数地址是编译时常量时,函数将与最新版本的 GCC 一起工作,优化间接调用,例如您调用 func( &amp;doNothing ) 而不是 func( some_func_ptr ) 其中 some_func_ptr 是一个在其他地方设置了值的变量。
  • 我刚刚检查了 g++,它优化了一切,lambda、对象和(使用 -O3)函数。

标签: c++ templates c++11 lambda callback


【解决方案1】:

只需传递一个空函子。

只要您打开了优化,编译器就会实例化模板,内联(空)对函子的调用,因此什么也不做。它应该优化到零,不要费心元编程来尝试删除调用。

我不会发誓 G++ 会以同样的方式优化“什么都不做”的 lambda,但它应该这样做,因为类型是已知的,并且它的函数调用运算符是内联的并且已知为空。

使用 lambda 没有固有的开销,它只是用 operator() 声明对象类型并创建该类型的临时对象的语法糖。编译器前端需要做很多工作才能完成所有这些工作,但是一旦类型存在,优化器就应该将其视为与执行相同操作的用户定义结构完全相同。出于这个原因,返回它也是安全的,它只是一个对象类型的实例,就像一个用户定义的函数对象。

【讨论】:

  • 我同意,如果它与一个简单的空函子一起工作,为什么还要为元编程混乱而烦恼呢?另一个问题:即使空的 lamda 或空的 operator() 带参数(它必须这样做,因为它有一个必需的签名),编译器会忽略这些参数并优化它们吗?
【解决方案2】:

只要您的 lambda 不通过引用捕获任何局部变量,就可以安全地返回它并稍后调用(对于空 lambda,它只是一个没有成员变量的可调用对象,因此可以安全地复制和返回) .

至于调用消除,由您的编译器决定 lambda 什么都不做并删除调用。

【讨论】:

    【解决方案3】:

    您可以尝试这种方法,它实现简单,没有代码重复,并且减轻了在每个调用点传递空 lambda 的痛苦:

    struct EmptyParam
    {
      void operator()(int a, int b, int c){}
    };
    
    template <class ReportFunc>
    void func (ReportFunc report_func)
    {
      int a = 0, b = 0, c = 0;
      for (/* ... */)
      {
        do_something (a, b);
        report_func (a, b, c);
        do_something_else (b, c);
      }
    }
    
    void func()
    {
      func<EmptyParam>(EmptyParam());
    }
    
    int _tmain(int argc, _TCHAR* argv[])
    {
      func([](int,int,int){});
      func();
        return 0;
    }
    

    编辑:为了完整起见,以下是完全避免调用 report_func 的版本。对于您的特定情况,它并没有比我提出的第一个解决方案更优化,只是另一种做事方式。就个人而言,我会采用上述解决方案:

    struct EmptyParam{};
    
    template <class ReportFunc>
    struct CallReportFunc
    {
      static void Call(const ReportFunc & report_func, int a, int b, int c)
      {
        report_func (a, b, c);
      }
    };
    
    template <>
    struct CallReportFunc<EmptyParam>
    {
      static void Call(const EmptyParam &/*report_func*/, int /*a*/, int /*b*/, int /*c*/)
      {
        // do nothing
      }
    };
    
    template <class ReportFunc>
    void func (ReportFunc report_func)
    {
      int a =0,b =0,c=0;
      for (;true;)
      {
        CallReportFunc<ReportFunc>::Call(report_func, a, b, c);
      }
    }
    
    void func()
    {
      func<EmptyParam>(EmptyParam());
    }
    
    int _tmain(int argc, _TCHAR* argv[])
    {
      func([](int,int,int){});
      func();
        return 0;
    }
    

    【讨论】:

    • 如果我只传递一个带有空 operator() 的函子怎么办?编译器不能消除调用(因为 operator() 反正什么都不做)?还是一个空的 lambda?会更简单
    • @fr33domlover 你的意思是func([](){});?模板无法编译,因为它试图用三个参数调用 report_func,但它没有。在我看来,没有参数的 func() 重载更干净。如果您的意思是func([](int,int,int){});,那么func(); 仍然更具可读性。
    • 我的意思是一个 operator() 或一个参数正确但函数体为空的 lambda。我不同意,重载意味着代码重复,我已经有了,我想避免重复
    • @Zadirion,所以你把它传递给func([](const TypeA&amp;, const TypeB&amp;, const TypeC&amp;){})
    • 所以定义struct Noop { template&lt;class... T&gt; void operator()(T&amp;&amp;...) const { } }; 并在每个呼叫站点(以及其他任何可能有用的地方)使用Noop{},这仍然比您的专业化代码少。让编译优化代码,把时间花在写有用的东西上。
    猜你喜欢
    • 1970-01-01
    • 2016-04-21
    • 1970-01-01
    • 2016-10-08
    • 1970-01-01
    • 1970-01-01
    • 2013-04-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多