Share via


C++'deki lambda ifadeleri

C++11 ve sonraki sürümlerde genellikle lambda olarak adlandırılan lambda ifadesi, çağrıldığı veya işleve bağımsız değişken olarak geçirildiği konumda anonim işlev nesnesi ( kapatma) tanımlamanın kullanışlı bir yoludur. Lambdalar genellikle algoritmalara veya zaman uyumsuz işlevlere geçirilen birkaç kod satırı kapsüllemek için kullanılır. Bu makale lambdaların ne olduğunu tanımlar ve bunları diğer programlama teknikleriyle karşılaştırır. Bunların avantajlarını açıklar ve bazı temel örnekler sağlar.

Lambda ifadesinin bölümleri

İşleve üçüncü bağımsız değişken std::sort() olarak geçirilen basit bir lambda aşağıdadır:

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

Bu çizimde lambda söz diziminin bölümleri gösterilmektedir:

Diagram that identifies the various parts of a lambda expression.

Lambda ifade örneği: [=]() mutable throw() -> int { return x+y; } [=] yakalama yan tümcesidir; C++ belirtiminde lambda-introducer olarak da bilinir. Parantez parametre listesi içindir. Değiştirilebilir anahtar sözcük isteğe bağlıdır. throw() isteğe bağlı özel durum belirtimidir. -> int isteğe bağlı sondaki dönüş türüdür. Lambda gövdesi küme ayraçlarının içindeki ifadeden oluşur veya x+y döndürür; Bunlar, görüntüden sonra daha ayrıntılı olarak açıklanmıştır.

  1. capture yan tümcesi (C++ belirtiminde lambda-introducer olarak da bilinir.)

  2. parametre listesi İsteğe bağlı. (Lambda bildirimcisi olarak da bilinir)

  3. değiştirilebilir belirtim İsteğe bağlı.

  4. özel durum belirtimi İsteğe bağlı.

  5. sondaki dönüş türü İsteğe bağlı.

  6. lambda gövdesi.

Capture yan tümcesi

Lambda, gövdesinde yeni değişkenler (C++14'te) tanıtabilir ve ayrıca çevresindeki kapsamdan değişkenlere erişebilir veya bunları yakalayabilir. Lambda yakalama yan tümcesiyle başlar. Hangi değişkenlerin yakalandığını ve yakalamanın değere göre mi yoksa başvuruya göre mi olduğunu belirtir. Ve işareti (&) ön ekini içeren değişkenlere başvuru yoluyla erişilir ve buna sahip olmayan değişkenlere değerle erişilir.

Boş bir capture yan tümcesi, [ ]lambda ifadesinin gövdesinin kapsayan kapsamda hiçbir değişkene erişmediğini gösterir.

Lambda gövdesinde başvurulan dış değişkenlerin nasıl yakalandığını belirtmek için capture-default modunu kullanabilirsiniz: [&] başvurduğunuz tüm değişkenlerin başvuruyla yakalandığı ve [=] bunların değerle yakalandığı anlamına gelir. Varsayılan yakalama modunu kullanabilir ve ardından belirli değişkenler için açıkça karşı modu belirtebilirsiniz. Örneğin, bir lambda gövdesi dış değişkene başvuruyla ve dış değişkene totalfactor değere göre erişiyorsa, aşağıdaki yakalama yan tümceleri eşdeğerdir:

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

Yakalama varsayılanı kullanıldığında yalnızca lambda gövdesinde bahsedilen değişkenler yakalanır.

Capture yan tümcesi capture-default &içeriyorsa, yakalama yan tümcesinin yakalamasında hiçbir tanımlayıcı biçiminde &identifierolamaz. Benzer şekilde, capture yan tümcesi capture-default =içeriyorsa, bu yakalama yan tümcesinin hiçbir yakalaması biçiminde =identifierolamaz. Tanımlayıcı veya this yakalama yan tümcesinde birden çok kez görüntülenemez. Aşağıdaki kod parçacığında bazı örnekler gösterilmektedir:

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
    [=, *this]{ }; // OK: captures this by value. See below.
    [i, i]{};      // ERROR: i repeated
}

Yakalama ve ardından üç nokta, bu variadic şablon örneğinde gösterildiği gibi bir paket genişletmesidir:

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

Bir sınıf üyesi işlevinin gövdesinde lambda ifadelerini kullanmak için, işaretçiyi yakalama yan tümcesine geçirerek this kapsayan sınıfın üye işlevlerine ve veri üyelerine erişim sağlayın.

Visual Studio 2017 sürüm 15.3 ve üzeri (mod ve sonraki sürümlerde /std:c++17 kullanılabilir): this yakalama yan tümcesinde belirtilerek *this işaretçi değere göre yakalanabilir. Değere göre yakalama, kapanışın tamamını lambda'nın çağrıldığı her çağrı sitesine kopyalar. (Kapanış, lambda ifadesini kapsülleyen anonim işlev nesnesidir.) Lambda paralel veya zaman uyumsuz işlemlerde yürütürse değere göre yakalama yararlıdır. Özellikle NUMA gibi bazı donanım mimarilerinde kullanışlıdır.

Sınıf üyesi işlevleriyle lambda ifadelerinin nasıl kullanılacağını gösteren bir örnek için lambda ifadelerinin örnekleri bölümünde "Örnek: Yöntemde lambda ifadesi kullanma" bölümüne bakın.

Capture yan tümcesini kullandığınızda, özellikle çok iş parçacıklı lambdaları kullanırken şu noktaları göz önünde bulundurmanızı öneririz:

  • Dış değişkenleri değiştirmek için başvuru yakalamaları kullanılabilir, ancak değer yakalamaları kullanılamaz. (mutable kopyaların değiştirilmesine izin verir, ancak özgün kopyalara izin vermez.)

  • Başvuru yakalamaları, dışındaki değişkenlere yönelik güncelleştirmeleri yansıtır, ancak değer yakalamaları yansıtmaz.

  • Başvuru yakalamaları bir yaşam süresi bağımlılığı sunar, ancak değer yakalamalarının yaşam süresi bağımlılıkları yoktur. Özellikle lambda zaman uyumsuz olarak çalıştığında önemlidir. Zaman uyumsuz bir lambdada başvuruya göre bir yerel yakalarsanız, lambda çalıştırıldığında bu yerel kolayca gitmiş olabilir. Kodunuz çalışma zamanında erişim ihlaline neden olabilir.

Genelleştirilmiş yakalama (C++14)

C++14'te, bu değişkenlerin lambda işlevinin kapsayan kapsamında bulunmasına gerek kalmadan yakalama yan tümcesinde yeni değişkenler tanıtabilir ve başlatabilirsiniz. Başlatma herhangi bir rastgele ifade olarak ifade edilebilir; yeni değişkenin türü, ifade tarafından üretilen türden çıkarılır. Bu özellik, çevresindeki kapsamdan yalnızca taşıma değişkenlerini (örneğin std::unique_ptr) yakalamanıza ve bunları bir lambdada kullanmanıza olanak tanır.

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

Parametre listesi

Lambdalar hem değişkenleri yakalayabilir hem de giriş parametrelerini kabul edebilir. Parametre listesi (Standart söz diziminde lambda bildirimcisi ) isteğe bağlıdır ve çoğu açıdan işlevin parametre listesine benzer.

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

C++14'te parametre türü genelse, tür tanımlayıcısı olarak anahtar sözcüğünü kullanabilirsinizauto. Bu anahtar sözcük, derleyiciye işlev çağrısı işlecini şablon olarak oluşturmasını söyler. Parametre listesindeki her örneği auto ayrı bir tür parametresine eşdeğerdir.

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

Bir lambda ifadesi, kendi bağımsız değişkeni olarak başka bir lambda ifadesini alabilir. Daha fazla bilgi için lambda ifadeleri örnekleri makalesindeki "Yüksek Sıralı Lambda İfadeleri" bölümüne bakın.

Parametre listesi isteğe bağlı olduğundan, lambda ifadesine bağımsız değişken geçirmezseniz ve lambda-bildirimcisi özel durum belirtimi, sondaki-dönüş-türü veya mutableiçermiyorsa boş parantezleri atlayabilirsiniz.

Değiştirilebilir belirtim

Genellikle bir lambda'nın işlev çağrısı işleci değere göre sabittir, ancak anahtar sözcüğün mutable kullanılması bunu iptal eder. Değiştirilebilir veri üyeleri oluşturmaz. Belirtim, mutable lambda ifadesinin gövdesinin değer tarafından yakalanan değişkenleri değiştirmesini sağlar. Bu makalenin devamında yer alan bazı örneklerde nasıl kullanılacağı mutablegösterilmektedir.

Özel durum belirtimi

Lambda ifadesinin noexcept herhangi bir özel durum oluşturmadığını belirtmek için özel durum belirtimini kullanabilirsiniz. Normal işlevlerde olduğu gibi, Microsoft C++ derleyicisi bir lambda ifadesi özel durum belirtimini noexcept bildirirse ve lambda gövdesi burada gösterildiği gibi bir özel durum oluşturursa C4297 uyarısını oluşturur:

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

Daha fazla bilgi için bkz . Özel durum belirtimleri (throw).

Dönüş türü

Lambda ifadesinin dönüş türü otomatik olarak çıkarılır. Sondaki dönüş türünü belirtmediğiniz sürece anahtar sözcüğünü auto kullanmanız gerekmez. Sondaki dönüş türü , sıradan bir işlevin veya üye işlevin dönüş türü bölümüne benzer. Ancak, dönüş türü parametre listesini izlemelidir ve dönüş türünden önce sondaki-dönüş türü anahtar sözcüğünü -> eklemeniz gerekir.

Lambda gövdesi yalnızca bir return deyimi içeriyorsa lambda ifadesinin dönüş türü bölümünü atlayabilirsiniz. Veya ifade bir değer döndürmezse. Lambda gövdesi bir dönüş deyimi içeriyorsa, derleyici dönüş ifadesinin türünden dönüş türünü döndürür. Aksi takdirde, derleyici dönüş türünü olarak voiddöndürür. Bu ilkeyi gösteren aşağıdaki örnek kod parçacıklarını göz önünde bulundurun:

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 isn't valid

Lambda ifadesi, kendi dönüş değeri olarak başka bir lambda ifadesi üretebilir. Daha fazla bilgi için lambda ifadelerinin örnekleri bölümünde "Yüksek sıralı lambda ifadeleri" bölümüne bakın.

Lambda gövdesi

Lambda ifadesinin lambda gövdesi bileşik bir deyimdir. Sıradan bir işlevin veya üye işlevin gövdesinde izin verilen her şeyi içerebilir. Hem sıradan bir işlevin hem de lambda ifadesinin gövdesi şu tür değişkenlere erişebilir:

  • Daha önce açıklandığı gibi kapsayan kapsamdan yakalanan değişkenler.

  • Parameters konumuna gidin.

  • Yerel olarak bildirilen değişkenler.

  • Sınıf veri üyeleri, bir sınıf içinde bildirildiğinde ve this yakalandığında.

  • Statik depolama süresi olan herhangi bir değişken(örneğin, genel değişkenler).

Aşağıdaki örnek, değişkeni değere göre açıkça yakalayan ve değişkeni n başvuruya göre örtük olarak yakalayan m bir lambda ifadesi içerir:

// 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;
}
5
0

Değişken n değer tarafından yakalandığından, değeri lambda ifadesine yapılan çağrıdan sonra kalır 0 . Belirtim mutable lambda içinde değiştirilmesini sağlar n .

Lambda ifadesi yalnızca otomatik depolama süresi olan değişkenleri yakalayabilir. Ancak, lambda ifadesinin gövdesinde statik depolama süresi olan değişkenleri kullanabilirsiniz. Aşağıdaki örnek, bir nesnedeki generate her öğeye bir değer atamak için işlevini ve lambda vector ifadesini kullanır. Lambda ifadesi sonraki öğenin değerini oluşturmak için statik değişkeni değiştirir.

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 isn't thread-safe and is shown for illustration only
}

Daha fazla bilgi için bkz . oluşturma.

Aşağıdaki kod örneği, önceki örnekteki işlevini kullanır ve C++ Standart Kitaplık algoritmasını generate_nkullanan bir lambda ifadesi örneği ekler. Bu lambda ifadesi, bir vector nesnenin öğesini önceki iki öğenin toplamına atar. mutable anahtar sözcüğü, lambda ifadesinin gövdesinin dış değişkenlerin x ve ylambda ifadesinin değere göre yakaladığı kopyalarını değiştirebilmesi için kullanılır. Lambda ifadesi özgün değişkenleri x ve y değere göre yakaladığından, lambda yürütülürken değerleri kalır 1 .

// 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 isn't 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);
}
vector v after call to generate_n() with lambda: 1 1 2 3 5 8 13 21 34
x: 1 y: 1
vector v after 1st call to fillVector(): 1 2 3 4 5 6 7 8 9
vector v after 2nd call to fillVector(): 10 11 12 13 14 15 16 17 18

Daha fazla bilgi için bkz . generate_n.

constexpr lambda ifadeleri

Visual Studio 2017 sürüm 15.3 ve üzeri (mod ve sonraki sürümlerde /std:c++17 kullanılabilir): Yakalanan veya tanıtılan her veri üyesinin başlatılmasına sabit bir ifade içinde izin verildiğinde lambda ifadesini olarak constexpr bildirebilirsiniz (veya sabit bir ifadede kullanabilirsiniz).

    int y = 32;
    auto answer = [y]() constexpr
    {
        int x = 10;
        return y + x;
    };

    constexpr int Increment(int n)
    {
        return [n] { return n + 1; }();
    }

Lambda, sonucu bir işlevin gereksinimlerini karşılıyorsa örtük olarak constexpr kullanılır constexpr :

    auto answer = [](int n)
    {
        return 32 + n;
    };

    constexpr int response = answer(10);

Lambda örtük veya açıkça constexprise, işlev işaretçisine dönüştürme bir constexpr işlev üretir:

    auto Increment = [](int n)
    {
        return n + 1;
    };

    constexpr int(*inc)(int) = Increment;

Microsoft'a özgü

Lambdalar, şu ortak dil çalışma zamanı (CLR) yönetilen varlıklarında desteklenmez: ref class, ref struct, value classveya value struct.

gibi __declspecMicrosoft'a özgü bir değiştirici kullanıyorsanız, bunu hemen sonrasında bir lambda ifadesine parameter-declaration-clauseekleyebilirsiniz. Örneğin:

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

Belirli bir değiştiricinin lambdalar tarafından desteklenip desteklenmediğini belirlemek için Microsoft'a özgü değiştiriciler bölümündeki değiştirici hakkındaki makaleye bakın.

Visual Studio, C++11 Standart lambda işlevselliğini ve durum bilgisi olmayan lambdaları destekler. Durum bilgisi olmayan lambda, rastgele çağırma kuralı kullanan bir işlev işaretçisine dönüştürülebilir.

Ayrıca bkz.

C++ Dil Başvurusu
C++ Standart Kitaplığındaki İşlev Nesneleri
İşlev Çağrısı
for_each