[英]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,請記住它們與任何其他對象相同。
無論如何,您現在擁有做出明智決定的所有關鍵(同樣,取決於您的代碼及其語義)。
一個 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.