2011-02-01 10 views
24

Załóżmy Mam funkcję functionProxy że trwa ogólny parametr function i wywołać jego operator():C++ czy kompilatory mogą wstawiać wskaźnik funkcji?

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

Obiekt przekazany do niego mogą być:

  • funktorem:

    struct Functor { 
        void operator()() const { 
         std::cout << "functor!" << std::endl; 
        } 
    }; 
    
  • funkcji: funkcję

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

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

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

będzie kompilator móc wbudować function zasięgu functionProxy we wszystkich powyższych przypadkach?

Odpowiedz

26

Oczywiście.

Wie, że wartość function jest taka sama jak wartość, którą przekazuje, zna definicję funkcji, więc po prostu zamienia definicję bezpośrednio i wywołuje funkcję bezpośrednio.

Nie mogę wymyślić warunku, w którym kompilator nie zainicjuje jednoliniowego wywołania funkcji, po prostu zastępuje wywołanie funkcji wywołaniem funkcji, bez możliwej utraty.


Biorąc pod uwagę ten kod:

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

Z MANUALLY_INLINE zdefiniowanym, otrzymujemy w ten sposób:

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 

A bez tego:

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 

samo. (Kompilacja z MSVC 2010, wydanie waniliowe.)

+1

Brzmi dobrze w zasadzie, ale to zdać spróbować i zobaczyć test? Niestety nie jestem zbyt zajęty/obecnie, żeby to sprawdzić. Bardzo ciekawy, jak mi się to przydarzyło. – Potatoswatter

+0

@Potatoswatter: Przeprowadzę teraz test, w jednej chwili. – GManNickG

+0

@Potatoswatter: Zaktualizowano dla Twojej przyjemności oglądania. – GManNickG

0

Prawdopodobnie. Nie ma żadnego silnego powodu ani przeciw niemu, zależy to tylko od tego, co zaimplementowali kompilatorzy.

-3

Czy kompilator jest w stanie włączyć połączenia? Tak.

Będzie to? Może. Sprawdź po know it matters.

-2

Funktor i lambda zostaną wstawione, ponieważ są to obiekty bezstanowe (wszystkie informacje są dostępne w czasie kompilacji).

Wskaźniki funkcji i zwiększanie :: Obiekty funkcji (teraz w standardzie: :) ​​nie można wstawiać, ponieważ nie jest jasne, w czasie kompilacji, jaką funkcję wskazują. Jeśli są const, mogą się różnić.

0

próbowali kod po matrycy pointer-to-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 i szczęk ++ 3.5.0 wszystko inlined oba widget.f() i (*lambda_)() połączeń za pomocą -O2. Oznacza to, że a został inkrementowany bezpośrednio wewnątrz main() według zdemontowanych plików binarnych.

Inlinowanie zostało zastosowane nawet ze stałymi wskaźnikami lambda i lambda_ (usunięcie obu const).

Ditto ze zmienną lokalną wychwytywania i lambda:

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