2015-11-30 10 views
8

co teraz, a potem wymyślić kodu tak:przechwytywanie wszystko vs. przechwytywanie tylko niektóre z lambda

// lazy lambda: 
[&] { 
// use references to a, b, c 
} 

// pedantic lambda 
[&a, &b, &c] { 
// use references to a, b, c 
} 

Chciałbym wiedzieć, który z lambdas jest lepszy pod względem wydajności, a może Rozmiar pliku wykonywalnego zgodny ze standardem C++ 14 (lub nowszym) i praktyczne doświadczenie z kompilatorami.

+0

Jeśli leniwy lambda kończy się przechwytywaniem tych samych zmiennych co pedantyczna lambda, nie widzę powodu, dla którego wydajność, a nawet generowane kody maszynowe powinny się różnić. – alain

+2

IDK, jeśli jest dozwolony zgodnie ze standardem lub nie, ale myślę, że kompilator przechwyciłby tylko zmienne, których używasz. – NathanOliver

+3

Różnica jest czysto syntaktyczna, a nie semantyczna. Obie lambdy robią to samo. –

Odpowiedz

3

Z wyjątkiem banalnego przypadku, w którym jawnie przechwytujesz coś, co nie jest wymienione w lambda, istnieją dwa przypadki narożne, w których może występować różnica.

Po pierwsze, niejawne przechwytywania generalnie nie przechwytują elementów, które nie są odr-używane (ale zobacz następny element wyjątku do tego). Obejmuje to, między innymi, rzeczy, o których mowa w unevaluated argumentów, takich jak te z decltype i sizeof, a także niektórych const i constexpr zmiennych lokalnych stosowane w niektórych kontekstach (skonsultować [basic.def.odr] do pełnego zestawu zasad):

void f(int){} 
void f2(const int &) {} 
void t() { 
    const int x = 8; 
    constexpr double y = 8; 
    const double z = 8; 
    auto g = []{ f(x); }; // OK 
    auto g1 = [=]{ f(x); }; // OK, does not capture x 

    // usually won't fire, though not guaranteed 
    static_assert(sizeof(g) == sizeof(g1), "!!"); 

    auto g2 = []{ f(y); }; // OK 
    auto g3 = []{ f(z); }; // Error, must capture z 

    auto g4 = []{ f2(x); }; // Error, must capture x since it's odr-used 
    auto g5 = [=]{ f2(x); }; // OK, captures x 
    auto g6 = []{ f2(+x); }; // OK, doesn't odr-use x 
    auto g7 = []{ f2(y); }; // OK 
} 

Jeśli ręcznie napiszesz listę przechwytywania, możliwe, że zrobisz więcej, niż potrzebujesz technicznie, ponieważ reguły określające, co jest lub nie są używane, są dość skomplikowane.

Po drugie, niejawne przechwytywanie w ogólnych lambdach uchwyci rzeczy używane w wyrażeniu zależnym, nawet jeśli niekoniecznie są one odr używane, dla zdrowia psychicznego. Pożyczanie an example from the standard:

void f(int, const int (&)[2] = {}) { } // #1 
void f(const int&, const int (&)[1]) { } // #2 
void test() { 
    const int x = 17; 
    auto g2 = [=](auto a) { 
    int selector[sizeof(a) == 1 ? 1 : 2]{}; 
    f(x, selector); // OK: is a dependent expression, so captures x 
        // Will actually odr-use x only if sizeof(a) == 1 
    }; 
} 

Jednak w rzeczywistości nie są wymagane, aby uchwycić coś, co może albo nie-może-być-odr wykorzystywane podczas pisania rodzajowe lambda; musisz go przechwycić tylko wtedy, gdy stworzysz specjalizację operatora wywołania funkcji, który używa odr. Jeśli więc nigdy nie wywołasz wynikowej ogólnej lambdy w sposób, który mógłby odr-użyć przedmiotowego problemu, to ukryte przechwytywanie może uchwycić więcej niż to, czego potrzebujesz. Na przykład, jest to dozwolone: ​​

void test() { 
    const int x = 17; 
    auto g3 = [](auto a) { // no capture 
    int selector[sizeof(a) == 1 ? 1 : 2]{}; 
    f(x, selector); // OK for now 
    }; 
} 

dopóki nie nazywamy g3 z niczego, którego rozmiar wynosi 1: g3(0) jest OK w typowych systemach; g3('\0') nie jest.

5

Nie ma różnicy w tym przykładzie. Kompilator przechwyci tylko zmienne, które są jawnie przywoływane w lambda, sprawiając, że leniwy chwyt jest równoznaczny z jawnym.

Leniwe przechwytywanie jest nieco wygodniejsze. Zmiana treści lambda w celu użycia dodatkowych przechwyconych zmiennych lub usunięcie wszystkich odwołań do przechwyconej zmiennej z istniejącej lambda, normalnie wymaga również aktualizacji jawnej listy przechwytywania. Z leniwym przechwytywaniem kompilator zrobi wszystko za Ciebie.

1

Jak wspomniano powyżej, nie będzie różnicy w odniesieniu do wygenerowanego kodu. Jednak uważam, że lepiej jest być wyraźnym - daje lepszą czytelność (łatwiejsze do zrozumienia o zmiennym cyklu życia), a także zapewnia lepszą obsługę kompilatora, tworząc błędy, gdy uzyskujesz dostęp do zmiennej, której nie chciałeś.

Uważam, że "leniwe chwytanie" nie jest wcale dobrą cechą.

Powiązane problemy