C++编译器能够内联函数指针吗?

31

假设我有一个函数 functionProxy,它接受一个泛型参数 function,并调用它的 operator() 方法:

template< typename Function > void functionProxy( Function function ) {
    function();
}
传递给它的对象可能是:
  • 一个函数对象:

struct Functor {
    void operator()() const {
        std::cout << "functor!" << std::endl;
    }
};
一个函数:
void function( ) {
    std::cout << "function!" << std::endl;
}
  • 一个(C++0x)lambda函数:

  • [](){ std::cout << "lambda!" << std::endl; }
    

    int main( )
    {
        functionProxy( Functor() );
        functionProxy( function );
        functionProxy( [](){ std::cout << "lambda!" << std::endl; } );
        return 0;
    }
    
    编译器是否能在上述所有情况下内联functionfunctionProxy中?
    4个回答

    35

    没问题。

    它知道function的值与其传递的值相同,知道函数的定义,所以只需内联替换定义并直接调用函数。

    我想不出编译器不会内联一行函数调用的情况,它只是用函数调用替换另一个函数调用,没有可能损失。


    给定以下代码:

    #include <iostream>
    
    template <typename Function>
    void functionProxy(Function function)
    {
        function();
    }
    
    struct Functor
    {
        void operator()() const
        {
            std::cout << "functor!" << std::endl;
        }
    };
    
    void function()
    {
        std::cout << "function!" << std::endl;
    }
    
    //#define MANUALLY_INLINE
    
    #ifdef MANUALLY_INLINE
    void test()
    {
        Functor()();
    
        function();
    
        [](){ std::cout << "lambda!" << std::endl; }();
    }
    #else
    void test()
    {
        functionProxy(Functor());
    
        functionProxy(function);
    
        functionProxy([](){ std::cout << "lambda!" << std::endl; });
    }
    #endif
    
    int main()
    {
        test();
    }
    

    如果定义了MANUALLY_INLINE,我们会得到这个结果:

    test:
    00401000  mov         eax,dword ptr [__imp_std::endl (402044h)]  
    00401005  mov         ecx,dword ptr [__imp_std::cout (402058h)]  
    0040100B  push        eax  
    0040100C  push        offset string "functor!" (402114h)  
    00401011  push        ecx  
    00401012  call        std::operator<<<std::char_traits<char> > (401110h)  
    00401017  add         esp,8  
    0040101A  mov         ecx,eax  
    0040101C  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (40204Ch)]  
    00401022  mov         edx,dword ptr [__imp_std::endl (402044h)]  
    00401028  mov         eax,dword ptr [__imp_std::cout (402058h)]  
    0040102D  push        edx  
    0040102E  push        offset string "function!" (402120h)  
    00401033  push        eax  
    00401034  call        std::operator<<<std::char_traits<char> > (401110h)  
    00401039  add         esp,8  
    0040103C  mov         ecx,eax  
    0040103E  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (40204Ch)]  
    00401044  mov         ecx,dword ptr [__imp_std::endl (402044h)]  
    0040104A  mov         edx,dword ptr [__imp_std::cout (402058h)]  
    00401050  push        ecx  
    00401051  push        offset string "lambda!" (40212Ch)  
    00401056  push        edx  
    00401057  call        std::operator<<<std::char_traits<char> > (401110h)  
    0040105C  add         esp,8  
    0040105F  mov         ecx,eax  
    00401061  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (40204Ch)]  
    00401067  ret  
    

    没加这个:

    test:
    00401000  mov         eax,dword ptr [__imp_std::endl (402044h)]  
    00401005  mov         ecx,dword ptr [__imp_std::cout (402058h)]  
    0040100B  push        eax  
    0040100C  push        offset string "functor!" (402114h)  
    00401011  push        ecx  
    00401012  call        std::operator<<<std::char_traits<char> > (401110h)  
    00401017  add         esp,8  
    0040101A  mov         ecx,eax  
    0040101C  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (40204Ch)]  
    00401022  mov         edx,dword ptr [__imp_std::endl (402044h)]  
    00401028  mov         eax,dword ptr [__imp_std::cout (402058h)]  
    0040102D  push        edx  
    0040102E  push        offset string "function!" (402120h)  
    00401033  push        eax  
    00401034  call        std::operator<<<std::char_traits<char> > (401110h)  
    00401039  add         esp,8  
    0040103C  mov         ecx,eax  
    0040103E  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (40204Ch)]  
    00401044  mov         ecx,dword ptr [__imp_std::endl (402044h)]  
    0040104A  mov         edx,dword ptr [__imp_std::cout (402058h)]  
    00401050  push        ecx  
    00401051  push        offset string "lambda!" (40212Ch)  
    00401056  push        edx  
    00401057  call        std::operator<<<std::char_traits<char> > (401110h)  
    0040105C  add         esp,8  
    0040105F  mov         ecx,eax  
    00401061  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (40204Ch)]  
    00401067  ret
    

    相同。(使用MSVC 2010编译,标准版本。)


    1
    原则上听起来不错,但是否通过了尝试和检查测试呢?不幸的是,我现在/这些天太忙了,没有时间检查。我很好奇,因为这发生在我身上。 - Potatoswatter
    2
    哦,不错!此外,当启用优化时,GCC会生成相同的代码。 - peoro
    @GMan:我想(除非LTO启动),函数必须定义为内联的(而不是在另一个转换单元中)吗? - Matthieu M.
    @Matt: 对我来说听起来是正确的。我想我不仅测试了调用是否内联,还测试了函数本身。如果您想要测试,我可以测试一下,但我怀疑如果没有内联被调用的函数的能力,它会尽最大努力将调用本身内联。 - GManNickG
    @GManNickG 嗨Nick,我之前看到了这个答案,而且现在我正在使用MSVC 2012时遇到了一些函数指针的问题,我想知道你能否帮助我!https://dev59.com/LHHYa4cB1Zd3GeqPReox 如果这样问有失礼,敬请谅解。如果需要,我会添加奖励!祝好 Christian - user896326
    显示剩余2条评论

    0
    尝试了以下模板化的指向lambda表达式的指针代码:
    volatile static int a = 0;
    
    template <typename Lambda> class Widget {
       public: 
          Widget(const Lambda* const lambda) : lambda_(lambda) { }
          void f() { (*lambda_)(); }
       private:
          const Lambda* const lambda_;
    };
    
    int main() {
       auto lambda = [](){ a++; };
       Widget<decltype(lambda)> widget(&lambda);
       widget.f();
    }
    

    GNU g++ 4.9.2、Intel icpc 16.0.1 和 clang++ 3.5.0 均使用 -O2widget.f()(*lambda_)()调用进行了内联。也就是说,根据反汇编二进制代码,a直接在main()中被增加。

    即使没有const修饰,也应用了非常数的lambdalambda_指针进行内联操作。

    对于局部变量和lambda捕获同样适用:

    int main() {
       volatile int a = 0;
       auto lambda = [&a](){ a++; };
       ...
    

    0
    可能吧。没有明确的支持或反对,这取决于编译器开发者实现的方式。

    -4

    编译器能够内联调用吗?是的。

    它会吗?也许。在你知道它很重要之后再检查。


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接