2011-02-01 14 views
24

Supongamos que tengo una función functionProxy que toma un parámetro genérico function y llamar a su operator():C++ ¿pueden los compiladores alinear un puntero de función?

template< typename Function > void functionProxy(Function function) { 
    function(); 
} 

El objeto pasado a la misma puede ser:

  • un funtor:

    struct Functor { 
        void operator()() const { 
         std::cout << "functor!" << std::endl; 
        } 
    }; 
    
  • una función: función

    void function() { 
        std::cout << "function!" << std::endl; 
    } 
    
  • un (C++ 0x) lambda:

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

int main() 
{ 
    functionProxy(Functor()); 
    functionProxy(function); 
    functionProxy([](){ std::cout << "lambda!" << std::endl; }); 
    return 0; 
} 

¿El compilador ser capaz de inline function dentro de functionProxy en todos los casos anteriores?

Respuesta

26

Claro.

Sabe que el valor de function es el mismo que el valor que lo pasa, conoce la definición de la función, por lo que simplemente reemplaza la definición en línea y llama a la función directamente.

No puedo pensar en una condición en la que un compilador no alinee una llamada de función de una sola línea, solo está reemplazando una llamada de función con una llamada a función, sin pérdida posible.


Teniendo en cuenta este código:

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

Con MANUALLY_INLINE definido, obtenemos lo siguiente:

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 

y por fuera, esto:

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 

El mismo. (Compilado con MSVC 2010, Versión vainilla.)

+1

Suena bien, en principio, pero lo hace pasar la oportunidad y ver la prueba? Desafortunadamente estoy demasiado ocupado en este momento/en estos días para comprobarlo. Aunque es muy curioso, ya que esto se me ha ocurrido. – Potatoswatter

+0

@Potatoswatter: Haré una prueba ahora, en un momento. – GManNickG

+0

@Potatoswatter: actualizado para su placer visual. – GManNickG

0

Posiblemente. No hay una razón fuerte a favor o en contra, solo depende de lo que hayan implementado los escritores del compilador.

-3

¿El compilador puede para alinear las llamadas? Sí.

¿Lo harás? Tal vez. Verifique después de usted know it matters.

-2

El Functor y el lambda estarán subrayados porque son objetos no estáticos (toda la información está disponible en tiempo de compilación).

Punteros de función y boost :: Los objetos de función (ahora en std: :) no se pueden insertar porque no está claro en el tiempo de compilación a qué función apuntan. Si son const, las cosas pueden variar.

0

han probado el siguiente código plantilla puntero-a-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, 16.0.1 Intel icpc y sonido metálico ++ 3.5.0 toda inline ambos widget.f() y (*lambda_)() llamadas usando -O2. Es decir, a se incrementó directamente dentro de main() de acuerdo con los binarios desensamblados.

El alineamiento se aplicó incluso con punteros lambda y lambda_ no const (eliminando ambos const).

Lo mismo ocurre con una variable y lambda captura locales:

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