[英]C++11 lambda returning lambda
這段代碼對於 JS 開發者來說並不是什么陌生的東西
function get_counter()
{
return (
function() {
var c = 0;
return function() { return ++c; };
})();
}
它基本上創建了一個,它創建了不同的枚舉器。 所以我想知道是否可以使用新的 lambda 語義在 C++11 中完成同樣的事情? 我最終編寫了這段 C++,不幸的是它不能編譯!
int main()
{
int c;
auto a = [](){
int c = 0;
return [&](){
cout << c++;
};
};
return 0;
}
所以我想知道是否有一種解決方法可以編譯它,如果有編譯器如何使此代碼正確運行? 我的意思是它必須創建單獨的枚舉器,但它也應該收集垃圾(未使用的 c 變量)。
順便說一下,我使用的是 VS2012 編譯器,它會生成此錯誤:
Error 2 error C2440: 'return' : cannot convert from 'main::<lambda_10d109c73135f5c106ecbfa8ff6f4b6b>::()::<lambda_019decbc8d6cd29488ffec96883efe2a>' to 'void (__cdecl *)(void)' c:\users\ali\documents\visual studio 2012\projects\test\test\main.cpp 25 1 Test
您的代碼有一個錯誤,因為它包含一個懸空引用; c
引用將引用外部 lambda 中的局部變量,當外部 lambda 返回時,該變量將被銷毀。
您應該使用mutable
按值 lambda 捕獲來編寫它:
auto a = []() {
int c = 0;
return [=]() mutable {
cout << c++;
};
};
這依賴於后標准擴展以允許在返回類型推導的 lambda 中使用多個語句; 如果包含多個語句,是否有理由不允許 lambda 推斷返回類型? 修復它的最簡單方法是提供一個參數,以便 lambda 只包含一個語句:
auto a = [](int c) {
return [=]() mutable {
cout << c++;
};
};
不幸的是,lambdas 中不允許使用默認參數,因此您必須將其稱為a(0)
。 或者,以可讀性為代價,您可以使用嵌套的 lambda 調用:
auto a = []() {
return ([](int c) {
return [=]() mutable {
cout << c++;
};
})(0);
};
它的工作方式是,當a
執行內部 lambda 時, a
所有引用的變量復制到其閉包類型的實例中,這里是這樣的:
struct inner_lambda {
int c;
void operator()() { cout << c++; }
};
閉包類型的實例然后由外部 lambda 返回,並且可以被調用並在調用時修改其c
副本。
總的來說,您的(固定)代碼被轉換為:
struct outer_lambda {
// no closure
struct inner_lambda {
int c; // by-value capture
// non-const because "mutable"
void operator()() { cout << c++; }
}
// const because non-"mutable"
inner_lambda operator()(int c) const {
return inner_lambda{c};
}
};
如果您將c
作為按引用捕獲保留,則將是:
struct outer_lambda {
// no closure
struct inner_lambda {
int &c; // by-reference capture
void operator()() const { cout << c++; } // const, but can modify c
}
inner_lambda operator()(int c) const {
return inner_lambda{c};
}
};
這里的inner_lambda::c
是對局部參數變量c
的懸空引用。
C++ 的一個自然限制是,一旦變量不再存在,通過引用捕獲的 lambda 就不能再使用捕獲的變量。 所以即使你讓它編譯,你也不能從它出現的函數中返回這個 lambda(它也恰好是一個 lambda,但這無關緊要),因為自動變量c
在返回時被銷毀。
我認為您需要的代碼是:
return [=]() mutable {
cout << c++;
};
我沒有測試過它,我不知道哪些編譯器版本支持它,但這是一個按值捕獲, mutable
的說捕獲的值可以由 lambda 修改。
因此,每次調用a
都會得到一個不同的計數器,其計數從 0 開始。每次調用該計數器時,它都會增加自己的c
副本。 據我了解 Javascript(不遠),這就是你想要的。
我認為問題在於編譯器無法推導出外部 lambda(分配給a
)的返回類型,因為它包含的不僅僅是一個簡單的一行返回。 但不幸的是,也沒有辦法明確說明內部 lambda 的類型。 所以你將不得不返回一個std::function
,它帶有一些額外的開銷:
int main()
{
int c;
auto a = []() -> std::function<void()> {
int c = 0;
return [=]() mutable {
std::cout << c++;
};
};
return 0;
}
當然,您必須按價值捕獲,就像史蒂夫在他的回答中已經解釋的那樣。
編輯:至於為什么確切的錯誤是它無法將返回的內部 lambda 轉換為void(*)()
(指向void()
函數的指針),我只有一些猜測,因為我對他們的 lambda 實現沒有太多了解,我不確定它是否穩定或符合標准。
但我認為 VC 至少嘗試推斷內部 lambda 的返回類型並意識到它返回一個可調用的。 但是后來它以某種方式錯誤地假設這個內部 lambda 沒有被捕獲(或者他們無法確定內部 lambda 的類型),所以他們只是讓外部 lambda 返回一個簡單的函數指針,如果內部 lambda 不會,這確實可以工作捕捉任何東西。
編輯:就像ecatmur在他的評論中所說的那樣,在創建實際的get_counter
函數(而不是 lambda)時,甚至需要返回std::function
,因為普通函數沒有任何自動返回類型推導。
您應該知道的第一件事是,即使您獲得了要編譯的語法,其語義也是不同的。 在通過引用捕獲的 C++ lambda 中,只捕獲一個普通引用,這不會延長由該引用綁定的對象的生命周期。 也就是說, c
的生命周期綁定到封閉 lambda 的生命周期:
int main()
{
int c;
auto a = [](){
int c = 0;
return [&](){
return ++c;
};
}(); // Note: added () as in the JS case
std::cout << a() << a();
return 0;
}
添加丟失的()
以便評估外部 lambda 后,您的問題是在評估完整表達式后,返回的 lambda 中通過引用持有的c
不再有效。
話雖如此,以額外的動態分配為代價(這相當於 JS 的情況)使其工作並不太復雜:
int main()
{
int c;
auto a = [](){
std::shared_ptr<int> c = std::make_shared<int>(0);
return [=](){
return ++(*c);
};
}(); // Note: added () as in the JS case
std::cout << a() << a();
return 0;
}
那應該編譯並按預期工作。 每當內部 lambda 被釋放( a
超出范圍)計數器將從內存中釋放。
這適用於 g++ 4.7
#include <iostream>
#include <functional>
std::function<int()> make_counter() {
return []()->std::function<int()> {
int c=0;
return [=]() mutable ->int {
return c++ ;
};
}();
}
int main(int argc, char * argv[]) {
int i = 1;
auto count1= make_counter();
auto count2= make_counter();
std::cout << "count1=" << count1() << std::endl;
std::cout << "count1=" << count1() << std::endl;
std::cout << "count2=" << count2() << std::endl;
std::cout << "count1=" << count1() << std::endl;
std::cout << "count2=" << count2() << std::endl;
return 0;
}
Valgrind 對此完全沒有抱怨。 每次我調用 make_counter 時,valgrind 都會報告一個額外的分配和釋放,所以我假設 lambda 元編程代碼正在為變量 c 插入內存的分配代碼(我想我可以檢查調試器)。 我想知道這是否符合 Cxx11 或只是特定於 g++。 Clang 3.0 不會編譯它,因為它沒有 std::function (也許我可以嘗試使用 boost 函數)。
我知道這已經晚了,但是在 C++14 及更高版本中,您現在可以初始化 lambda 捕獲,從而生成更簡單的代碼:
auto a = []() {
return [c=0]() mutable {
cout << c++;
};
};
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.