簡體   English   中英

C++11 lambdas 如何表示和傳遞?

[英]How are C++11 lambdas represented and passed?

在 c++11 中傳遞 lambda 非常容易:

func( []( int arg ) {
  // code
} ) ;

但我想知道,將 lambda 傳遞給這樣的函數的成本是多少? 如果 func 將 lambda 傳遞給其他函數會怎樣?

void func( function< void (int arg) > f ) {
  doSomethingElse( f ) ;
}

lambda 的傳遞代價高昂嗎? 由於一個function對象可以被賦值為 0,

function< void (int arg) > f = 0 ; // 0 means "not init" 

這讓我認為函數對象有點像指針。 但是如果不使用new ,則意味着它們可能類似於值類型的struct或類,默認為堆棧分配和成員方式復制。

當您“按值”傳遞函數對象時,C++11“代碼體”和捕獲的變量組是如何傳遞的? 代碼體是否有很多多余的副本? 我是否必須標記通過const&傳遞的每個function對象const&以便不制作副本:

void func( const function< void (int arg) >& f ) {
}

或者函數對象以某種方式傳遞與常規 C++ 結構不同?

免責聲明:與現實相比,我的回答有些簡化(我把一些細節放在一邊),但大局就在這里。 此外,標准沒有完全指定 lambdas 或std::function必須如何在內部實現(實現具有一定的自由度),因此,就像對實現細節的任何討論一樣,您的編譯器可能會也可能不會完全按照這種方式進行。

但同樣,這是一個與 VTables 非常相似的主題:標准沒有太多要求,但任何明智的編譯器仍然很可能這樣做,所以我相信值得深入研究一下。 :)


拉姆達

實現 lambda 最直接的方法是一種未命名的struct

auto lambda = [](Args...) -> Return { /*...*/ };

// roughly equivalent to:
struct {
    Return operator ()(Args...) { /*...*/ }
}
lambda; // instance of the unnamed struct

就像任何其他類一樣,當你傳遞它的實例時,你永遠不必復制代碼,只需復制實際數據(這里根本沒有)。


按值捕獲的對象被復制到struct

Value v;
auto lambda = [=](Args...) -> Return { /*... use v, captured by value...*/ };

// roughly equivalent to:
struct Temporary { // note: we can't make it an unnamed struct any more since we need
                   // a constructor, but that's just a syntax quirk

    const Value v; // note: capture by value is const by default unless the lambda is mutable
    Temporary(Value v_) : v(v_) {}
    Return operator ()(Args...) { /*... use v, captured by value...*/ }
}
lambda(v); // instance of the struct

同樣,傳遞它僅意味着您傳遞數據 ( v ) 而不是代碼本身。


同樣,通過引用捕獲的對象被引用到struct

Value v;
auto lambda = [&](Args...) -> Return { /*... use v, captured by reference...*/ };

// roughly equivalent to:
struct Temporary {
    Value& v; // note: capture by reference is non-const
    Temporary(Value& v_) : v(v_) {}
    Return operator ()(Args...) { /*... use v, captured by reference...*/ }
}
lambda(v); // instance of the struct

這幾乎是 lambda 本身的全部(除了我省略的少數實現細節,但這些細節與理解它的工作原理無關)。


std::function

std::function是任何類型函子(lambda、獨立/靜態/成員函數、我展示的函子類等)的通用包裝器。

std::function的內部結構非常復雜,因為它們必須支持所有這些情況。 根據函子的確切類型,這至少需要以下數據(給出或獲取實現細節):

  • 指向獨立/靜態函數的指針。

要么,

  • 指向函子副本[請參閱下面的注釋]的指針(動態分配以允許任何類型的函子,正如您正確指出的那樣)。
  • 指向要調用的成員函數的指針。
  • 一個指向分配器的指針,它能夠復制函子和它自己(因為可以使用任何類型的函子,指向函子的指針應該是void* ,因此必須有這樣的機制——可能使用多態性. 基類 + 虛方法,派生類在template<class Functor> function(Functor)構造template<class Functor> function(Functor)本地生成)。

因為它事先不知道它必須存儲哪種函子(這很明顯,因為std::function可以重新分配),所以它必須處理所有可能的情況並在運行時做出決定。

注意:我不知道標准在哪里要求它,但這絕對是一個新副本,底層仿函數沒有共享:

int v = 0;
std::function<void()> f = [=]() mutable { std::cout << v++ << std::endl; };
std::function<void()> g = f;

f(); // 0
f(); // 1
g(); // 0
g(); // 1

因此,當您傳遞std::function ,它至少涉及這四個指針(實際上在 GCC 4.7 64 位sizeof(std::function<void()>是 32,這是四個 64 位指針)和可選的動態函子的分配副本(正如我已經說過的,它只包含捕獲的對象,您不復制代碼)。


回答問題

將 lambda 傳遞給這樣的函數的成本是多少? [問題的背景:按價值]

好吧,正如您所看到的,它主要取決於您的函子(手工制作的struct函子或 lambda)及其包含的變量。 與直接按值傳遞struct functor 相比,開銷可以忽略不計,但它當然比按引用傳遞struct functor 高得多。

我是否必須標記通過const&傳遞的每個函數對象const&以便不制作副本?

恐怕這很難以通用的方式回答。 有時您會希望通過const引用、有時通過值、有時通過右值引用傳遞,以便您可以移動它。 這實際上取決於您的代碼的語義。

關於您應該選擇哪一個的規則是一個完全不同的主題 IMO,請記住它們與任何其他對象相同。

無論如何,您現在擁有做出明智決定的所有關鍵(同樣,取決於您的代碼及其語義)。

另請參閱C++11 lambda 實現和內存模型

一個 lambda 表達式就是:一個表達式。 編譯后,它會在運行時生成一個閉包對象。

5.1.2 Lambda 表達式 [expr.prim.lambda]

對 lambda 表達式的求值產生一個 prvalue 臨時值 (12.2)。 這個臨時對象稱為閉包對象。

對象本身是實現定義的,可能因編譯器而異。

這是在 clang https://github.com/faisalv/clang-glambda中 lambdas 的原始實現

如果 lambda 可以作為一個簡單的函數(即它不捕獲任何東西),那么它的制作方式完全相同。 特別是標准要求它與具有相同簽名的舊式函數指針兼容。 [編輯:它不准確,請參閱評論中的討論]

其余的取決於實施,但我不擔心。 最直接的實現只是攜帶信息。 與您在捕獲中要求的一樣多。 所以效果和你手動創建一個類是一樣的。 或者使用一些 std::bind 變體。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM