Výrazy lambda v jazyce C++

 

Nejnovější dokumentaci k sadě Visual Studio 2017 najdete tady.

V C ++ 11 výrazu lambda – často se označuje jako lambda– je vhodný způsob pro definování objektu anonymní funkce doprava na místo, kde je volána, nebo jako argument předaný funkci. Lambda výrazy se obvykle používají k zapouzdření několika řádků kódu, které jsou předány algoritmy nebo asynchronní metody. Tento článek definuje výrazy lambda, porovnává je s jinými programovacími technikami, popisuje jejich výhody a představuje základní příklad.

ISO C++ Standard uvádí jednoduchý lambda, který je předán jako třetí argument std::sort() funkce:

#include <algorithm>  
#include <cmath>  
  
void abssort(float* x, unsigned n) {  
    std::sort(x, x + n,  
        // Lambda expression begins  
        [](float a, float b) {  
            return (std::abs(a) < std::abs(b));  
        } // end of lambda expression  
    );  
}  
  

Tento obrázek ukazuje části lambda:

Konstrukční prvky pro lambda výrazy

  1. zachycení klauzule (také označované jako lambda představení ve specifikaci jazyka C++.)

  2. seznam parametrů volitelné. (Také označované jako lambda deklarátor)

  3. měnitelný specifikace volitelné.

  4. Výjimka – specifikace volitelné.

  5. koncové typ return volitelné.

  6. lambda textu)

Klauzule zachycení

Lambda, může zavést nové proměnné v jeho obsahu (v C ++ 14), a také může přistupovat – nebo zachytit--proměnné z okolního oboru. Lambda začíná klauzuli sběr dat (lambda představení v standardní syntaxe), která určuje, které proměnné jsou zachyceny a zda je sběru hodnotu nebo odkazem. Proměnné, které mají ampersand (&) předpona přistupují odkaz a proměnné, které nemají přistupují hodnotu.

Klauzuli prázdné zachycení [ ], označuje, že tělo výrazu lambda přistupuje žádné proměnné v nadřazených oboru.

Můžete použít výchozí režim sběr dat (capture-default v standardní syntaxe) označuje, jak můžete snadno zachytit mimo proměnné, které jsou uvedeny v argument lambda: [a] odkazují na prostředky, které jsou zachyceny všechny proměnné odkazující na a [=] znamená, že jsou zachyceny hodnotu. Můžete použít výchozí režim sběr dat a poté zadejte opačné režim explicitně pro konkrétní proměnné. Například pokud lambda textu přistupuje externí proměnné total odkazem a externí proměnné factor podle hodnoty, pak následující klauzule sběr dat jsou ekvivalentní:

  
[&total, factor]  
[factor, &total]  
[&, factor]  
[factor, &]  
[=, &total]  
[&total, =]  

Pouze proměnné, které jsou uvedeny v argument lambda jsou zachyceny po capture-default se používá.

Pokud zachycení klauzule obsahuje capture-default &, pak žádné identifier v capture tohoto zachycení klauzule může mít formulář & identifier. Podobně platí Pokud klauzule zachycení obsahuje capture-default =, pak žádné capture tohoto zachycení klauzule může mít formulář = identifier. Identifikátor nebo this nemůže být více než jednou použit v klauzuli zachycení. Následující fragment kódu dokládá několik příkladů.

struct S { void f(int i); };  
  
void S::f(int i) {  
    [&, i]{};    // OK  
    [&, &i]{};   // ERROR: i preceded by & when & is the default  
    [=, this]{}; // ERROR: this when = is the default  
    [i, i]{};    // ERROR: i repeated  
}  

Objekt capture za nímž následuje třemi tečkami je sada růst, jak je znázorněno v tomto variadic šablona příklad:

template<class... Args>  
void f(Args... args) {  
    auto x = [args...] { return g(args...); };  
    x();  
}  

Chcete-li použít lambda výrazy v těle metody třídy, předejte this ukazatel na klauzuli sběr dat k poskytování přístupu k metody a data členů ohraničující třídy. Příklad ukazuje, jak lze pomocí metody třídy lambda výrazy, naleznete v části "Příklad: Lambda výraz v metodou" v Příklady výrazů lambda.

Pokud použijete klauzuli sběr dat, doporučujeme, abyste zachování těchto bodů na paměti, zejména pokud použijete lambda výrazy s multithreadingu:

  • Zachycení odkazu lze použít pro úpravy proměnných mimo, ale nelze zachycení hodnotu. (mutable umožňuje kopií, které chcete upravit, ale není původní.)

  • Odkaz na zachycení odrážejí aktualizace na proměnné mimo, ale zachycení hodnotu nepodporují.

  • Referenční zachycení zavést závislost na životnost, ale zachycení hodnotu nemají žádné závislosti životnosti. To je obzvláště důležité, když argument lambda spustíte asynchronně. Pokud sbíráte místní odkazem v metodě asynchronní lambda, že místní bude velmi pravděpodobně být trvale není k dispozici podle času argument lambda běží, výsledkem narušení přístupu v době běhu.

Obecný zachycení (C++ 14)

V C ++ 14 lze zavádět a inicializace nové proměnné v klauzuli sběr dat, aniž by bylo nutné mít těchto proměnných existovat v oboru nadřazených lambda funkci. Inicializace může být vyjádřen jako libovolný libovolný výraz; Typ nové proměnné je odvozen od typu vytvořeného výraz. Výhodou této funkce je, že v C ++ 14 můžete zachytit jen přesunout proměnné (například std::unique_ptr) z oboru okolního a jejich použití v lambda.

pNums = make_unique<vector<int>>(nums);  
//...  
      auto a = [ptr = move(pNums)]()  
        {  
           // use ptr  
        };  

Seznam parametrů

Kromě zachycení proměnné, může přijmout lambda vstupní parametry. Seznam parametrů (lambda deklarátor v standardní syntaxe) je volitelná a ve většině aspekty vypadat například takto: seznam parametrů pro funkci.

int y = [] (int first, int second)  
{  
    return first + second;  
};  
  

V C++ 14, pokud je obecný typ parametru, můžete použít klíčové slovo auto jako specifikátor typu. Sdělí kompilátor vytvoří operátor volání funkce jako šablonu. Každá instance automaticky v seznamu parametrů je ekvivalentní distinct typ parametru.

auto y = [] (auto first, auto second)  
{  
    return first + second;  
};  

Výraz lambda může jako svůj argument přijmout jiný výraz lambda. Další informace naleznete v tématu "Vyšší pořadí Lambda výrazy" v tématu Příklady výrazů lambda.

Vzhledem k tomu, že je volitelný seznam parametrů, můžete vynechat závorky, pokud není předáním argumentů výrazu lambda a jeho lambda-declarator: neobsahuje Výjimka – specifikace, koncové typ return, nebo mutable.

Proměnlivé specifikace

Operátor volání funkcí lambda je obvykle b podle hodnot, ale využívání mutable klíčové slovo zruší tento. Nevytváří proměnlivé datové členy. Proměnlivé specifikace umožňují hlavní části výrazu lambda upravit proměnné, které jsou zachyceny hodnotou. Některé příklady dále v tomto článku ukazují, jak používat mutable.

Specifikace výjimek

Můžete použít throw() výjimky specifikace označuje, že výrazu lambda nevyvolá všechny výjimky. Jako pomocí běžných funkcí kompilátor Visual C++ generuje varování C4297 Pokud výrazu lambda deklaruje throw() specifikace výjimky a textu lambda vyvolá výjimku, jak je znázorněno zde:

// throw_lambda_expression.cpp  
// compile with: /W4 /EHsc   
int main() // C4297 expected  
{  
   []() throw() { throw 5; }();  
}  

Další informace naleznete v tématu Specifikace výjimek (throw).

Návratový typ

Návratový typ výrazu lambda je automaticky odvozen. Není třeba používat automaticky klíčové slovo neurčíte koncové typ return. Koncové typ return vypadat například takto: typ return část běžných metody nebo funkce. Však návratový typ musí následovat seznam parametrů, a musí obsahovat klíčové slovo koncové typ return -> před návratový typ.

Typ return část výrazu lambda lze vynechat, pokud lambda text obsahuje pouze jeden příkaz return nebo výraz nevrací hodnotu. Pokud lambda text obsahuje jeden příkaz return, kompilátor deduces návratový typ z typu vrácené výrazem. V opačném kompilátor deduces návratový typ, který má být void. Zvažte následující příklad fragmentu kódu, který ilustruje tento princip.

auto x1 = [](int i){ return i; }; // OK: return type is int  
auto x2 = []{ return{ 1, 2 }; };  // ERROR: return type is void, deducing   
                                  // return type from braced-init-list is not valid  
  

Výraz lambda může jako vrácenou hodnotu vytvořit jiný výraz lambda. Další informace naleznete v části "Vyšší pořadí Lambda výrazy" v Příklady výrazů lambda.

Hlavní část výrazu lambda

Datové části lambda (složeného prohlášení v standardní syntaxe) z lambda výraz může obsahovat cokoli, co může obsahovat text běžných metody nebo funkce. Text běžných funkce a výrazu lambda přístup těchto druhů proměnné:

  • Zaznamenat proměnné z vnějšího oboru, jak je popsáno výše.

  • Parametry

  • Místně deklarované proměnné

  • Třídy datových členů, pokud deklarován uvnitř třídy a this zaznamenat

  • Každá proměnná, která má dobu trvání statické úložiště – například globální proměnné

Následující příklad obsahuje výraz lambda, která explicitně zaznamená proměnné n podle hodnoty a implicitně zaznamená proměnné m odkazem:

// captures_lambda_expression.cpp  
// compile with: /W4 /EHsc   
#include <iostream>  
using namespace std;  
  
int main()  
{  
   int m = 0;  
   int n = 0;  
   [&, n] (int a) mutable { m = ++n + a; }(4);  
   cout << m << endl << n << endl;  
}  

Výstup:

5
0 Protože proměnné n zaznamenat podle hodnoty, zůstane její hodnota 0 Po volání výrazu lambda. mutable Povoluje specifikace n má být upraven v rámci argument lambda.

Přestože výraz lambda může zachytit pouze proměnné, které mají automatickou dobu ukládání, můžete použít proměnné, které mají statickou dobu ukládání v hlavní části výrazu lambda. V následujícím příkladu generate funkce a přiřadit hodnotu pro každý prvek ve výrazu lambda vector objektu. Výraz lambda změní statickou proměnnou a vygeneruje tak hodnotu dalšího prvku.

void fillVector(vector<int>& v)  
{  
    // A local static variable.  
    static int nextValue = 1;  
  
    // The lambda expression that appears in the following call to  
    // the generate function modifies and uses the local static   
    // variable nextValue.  
    generate(v.begin(), v.end(), [] { return nextValue++; });   
    //WARNING: this is not thread-safe and is shown for illustration only  
}  

Další informace naleznete v tématu generate.

Následující příklad kódu používá funkci z předchozího příkladu a přidá příklad výrazu lambda, která používá algoritmus STL generate_n. Tento výraz lambda přiřadí element vector objekt, který má součet předchozí dva elementy. mutable Klíčové slovo je použito tak, aby datové části výrazu lambda můžete upravovat jeho kopie externí proměnné x a y, která výrazu lambda zachycuje podle hodnoty. Vzhledem k tomu, že výrazu lambda zaznamená původní proměnné x a y podle hodnoty, jejich hodnoty zůstávají 1 Po provedení argument lambda.

// compile with: /W4 /EHsc  
#include <algorithm>  
#include <iostream>  
#include <vector>  
#include <string>  
  
using namespace std;  
  
template <typename C> void print(const string& s, const C& c) {  
    cout << s;  
  
    for (const auto& e : c) {  
        cout << e << " ";  
    }  
  
    cout << endl;  
}  
  
void fillVector(vector<int>& v)  
{  
    // A local static variable.  
    static int nextValue = 1;  
  
    // The lambda expression that appears in the following call to  
    // the generate function modifies and uses the local static   
    // variable nextValue.  
    generate(v.begin(), v.end(), [] { return nextValue++; });  
    //WARNING: this is not thread-safe and is shown for illustration only  
}  
  
int main()  
{  
    // The number of elements in the vector.  
    const int elementCount = 9;  
  
    // Create a vector object with each element set to 1.  
    vector<int> v(elementCount, 1);  
  
    // These variables hold the previous two elements of the vector.  
    int x = 1;  
    int y = 1;  
  
    // Sets each element in the vector to the sum of the   
    // previous two elements.  
    generate_n(v.begin() + 2,  
        elementCount - 2,  
        [=]() mutable throw() -> int { // lambda is the 3rd parameter  
        // Generate current value.  
        int n = x + y;  
        // Update previous two values.  
        x = y;  
        y = n;  
        return n;  
    });  
    print("vector v after call to generate_n() with lambda: ", v);  
  
    // Print the local variables x and y.  
    // The values of x and y hold their initial values because   
    // they are captured by value.  
    cout << "x: " << x << " y: " << y << endl;  
  
    // Fill the vector with a sequence of numbers  
    fillVector(v);  
    print("vector v after 1st call to fillVector(): ", v);  
    // Fill the vector with the next sequence of numbers  
    fillVector(v);  
    print("vector v after 2nd call to fillVector(): ", v);  
}  
  

Výstup:

vektoru v po volání generate_n() s lambda: 1 1 2 3 5 8 13 21 34
x: le 1: 1
vektoru v po 1. volání fillVector(): 1 2 3 4 5 6 7 8 9
vektoru v po druhé volání fillVector(): 10 11 12 13 14 15 16 17 18 Další informace naleznete v tématu generate_n.

Lambda výrazy nejsou podporovány v následujících běžné entity (CLR runtime) spravovaného jazyka: ref class, ref struct, value class, nebo value struct.

Pokud například používáte modifikátor specifické pro společnost Microsoft __declspec, můžete jej přidat do výrazu lambda ihned po parameter-declaration-clause– například:

auto Sqr = [](int t) __declspec(code_seg("PagedMem")) -> int { return t*t; };  
  

Chcete-li zjistit, zda je modifikátor podporován lambda výrazy, naleznete v článku o jej v Modifikátory specifické pro společnost Microsoft části dokumentace.

Visual Studio podporuje C ++ 11 standardní lambda výraz syntaxi a funkce, s následujícími výjimkami:

  • Podobně jako všechny ostatní třídy není lambda výrazy získejte konstruktorů automaticky vygenerované přesunout a přesunout operátory přiřazení. Další informace týkající se podpory rvalue odkaz chování, naleznete v části "Odkazy Rvalue" v Podpora funkcí C++11/14/17.

  • Nepovinný atribut specifikátor sekvence není podporováno v této verzi.

Visual Studio obsahuje tyto funkce kromě C ++ 11 standardní lambda funkce:

  • Bezstavové lambda výrazy, které jsou omni-lze převést na typ ukazatele funkce, které používají libovolný volání konvencí.

  • Automaticky odvozen návratové typy pro lambda subjekty, které jsou složitější než { return expression; }, tak dlouho, dokud všechny vrátíte se příkazy stejného typu. (Tato funkce je součástí navržené C ++ 14 standardu.)

Referenční dokumentace jazyka C++
Funkce objektů v STL
Volání funkcí
for_each

Zobrazit: