c++和closure(閉包)
一、定義: 是帶有上下文的函數。說白了,就是有狀態的函數。必須有上下文才能使用。
函數, 帶上了狀態, 就變成了閉包了. 什么叫 "帶上狀態" 呢? 意思是這個閉包有屬于自己的變量, 這些個變量的值是創建閉包的時候設置的, 并在調用閉包的時候, 可以訪問這些變量.
函數是代碼, 狀態是一組變量 ,將代碼和一組變量捆綁 (bind) , 就形成了閉包 ,內部包含 static 變量的函數, 不是閉包, 因為這個 static 變量不能捆綁. 你不能捆綁不同的 static 變量. 這個在編譯的時候已經確定了.
函數, 帶上了狀態, 就變成了閉包了. 什么叫 "帶上狀態" 呢? 意思是這個閉包有屬于自己的變量, 這些個變量的值是創建閉包的時候設置的, 并在調用閉包的時候, 可以訪問這些變量.
函數是代碼, 狀態是一組變量 ,將代碼和一組變量捆綁 (bind) , 就形成了閉包 ,內部包含 static 變量的函數, 不是閉包, 因為這個 static 變量不能捆綁. 你不能捆綁不同的 static 變量. 這個在編譯的時候已經確定了.
二、c++實現閉包的方法:
1、重載 operator() 例子
2、std::bind
3、lambda表達式,語法形式如下: [函數對象參數] (操作符重載函數參數) mutable或exception聲明 -> 返回值類型 { …函數體…}
1、重載 operator() 例子
class MyFunctor { public: MyFunctor(float f) : round(f) {} int operator()(float f) { return f + round; } private: float round; }; float round = 0.5; MyFunctor f(round);
2、std::bind
int my_func(float f, float round) { return f + round; } float round = 0.5; std::function<int(float,float)> f = my_func;
3、lambda表達式,語法形式如下: [函數對象參數] (操作符重載函數參數) mutable或exception聲明 -> 返回值類型 { …函數體…}
[函數對象參數],例如[&,a,b]標識一個Lambda的開始,這部分必須存在,不能省略。函數對象參數是傳遞給編譯器自動生成的函數對象類的構造函數的。
函數對象參數有以下形式:
[ ] 空沒有使用任何函數對象參數。
[=] 函數體內可以使用Lambda所在作用范圍內所有可見的局部變量(包括Lambda所在類的this),并且是值傳遞方式(相當于編譯器自動為我們按值傳遞了所有局部變量)。
[&] 函數體內可以使用Lambda所在作用范圍內所有可見的局部變量(包括Lambda所在類的this),并且是引用傳遞方式(相當于編譯器自動為我們按引用傳遞了所有局部變量)。
[this] 函數體內可以使用Lambda所在類中的成員變量。
[a] 將a按值進行傳遞。按值進行傳遞時,函數體內不能修改傳遞進來的a的拷貝,因為默認情況下函數是const的。要修改傳遞進來的a的拷貝,可以添加mutable修飾符。
[&a] 將a按引用進行傳遞。
[a, &b] 將a按值進行傳遞,b按引用進行傳遞。
[=,&a, &b] 除a和b按引用進行傳遞外,其他參數都按值進行傳遞。
[&, a, b] 除a和b按值進行傳遞外,其他參數都按引用進行傳遞。
(操作符重載函數參數),例如(int a,int &b)標識重載的()操作符的參數,沒有參數時,這部分可以省略。參數可以通過按值(如:(a,b))和按引用(如:(&a,&b))兩種方式進行傳遞
mutable與exception聲明,例如 mutable throw(),可省略
按值傳遞函數對象參數時,加上mutable修飾符后,可以修改按值傳遞進來的拷貝(注意是能修改拷貝,而不是值本身,如果沒有添加mutable,相當于對函數參數的增加了const修飾,無法修改參數)。exception聲明用于指定函數拋出的異常,如拋出整數類型的異常,可以使用throw(int)。
示例:
->返回值類型,例如 ->int 表示返回 int類型
標識函數返回值的類型,當返回值為void,或者函數體中只有一處return的地方(此時編譯器可以自動推斷出返回值類型)時,這部分可以省略。
{函數體},例如{cout<<“abc”;},不可省略,可以為空
// 無函數對象參數,輸出:1 2 { for_each(vctTemp.begin(), vctTemp.end(), [](int v){ cout << v << endl; }); }
// 以值方式傳遞作用域內所有可見的局部變量(包括this),輸出:11 12 { int a = 10; for_each(vctTemp.begin(), vctTemp.end(), [=](int v){ cout << v+a << endl; }); }
// 以引用方式傳遞作用域內所有可見的局部變量(包括this),輸出:11 13 12 { int a = 10; for_each(vctTemp.begin(), vctTemp.end(), [&](int v)mutable{ cout << v+a << endl; a++; }); cout << a << endl; }
// 以值方式傳遞局部變量a,輸出:11 13 10 { int a = 10; for_each(vctTemp.begin(), vctTemp.end(), [a](int v)mutable{ cout << v+a << endl; a++; }); cout << a << endl; }
// 以引用方式傳遞局部變量a,輸出:11 13 12 { int a = 10; for_each(vctTemp.begin(), vctTemp.end(), [&a](int v){ cout << v+a << endl; a++; }); cout << a << endl; }
// 傳遞this,輸出:21 22 { for_each(vctTemp.begin(), vctTemp.end(), [this](int v){ cout << v+m_nData << endl; }); }
// 除b按引用傳遞外,其他均按值傳遞,輸出:11 12 17 { int a = 10; int b = 15; for_each(vctTemp.begin(), vctTemp.end(), [=, &b](int v){ cout << v+a << endl; b++; }); cout << b << endl; }
int temp = 10;
vector<int> ivec = {30, -10, -20, 50, 40 ,100, -50};
std::sort(ivec.begin(), ivec.end(), [](const int &x, const int &y) {return abs(x) < abs(y);});
std::for_each(ivec.begin(), ivec.end(), [&](int &x) { x += temp; cout << x << endl;});
三、注意事項:
四、閉包(匿名函數)用處,可以是流程更清晰,易于理解,一般不能單獨使用,必須有上下文,閉包里處理的是上下文中的一些變量。一般情況下不能單獨使用
函數對象參數有以下形式:
[ ] 空沒有使用任何函數對象參數。
[=] 函數體內可以使用Lambda所在作用范圍內所有可見的局部變量(包括Lambda所在類的this),并且是值傳遞方式(相當于編譯器自動為我們按值傳遞了所有局部變量)。
[&] 函數體內可以使用Lambda所在作用范圍內所有可見的局部變量(包括Lambda所在類的this),并且是引用傳遞方式(相當于編譯器自動為我們按引用傳遞了所有局部變量)。
[this] 函數體內可以使用Lambda所在類中的成員變量。
[a] 將a按值進行傳遞。按值進行傳遞時,函數體內不能修改傳遞進來的a的拷貝,因為默認情況下函數是const的。要修改傳遞進來的a的拷貝,可以添加mutable修飾符。
[&a] 將a按引用進行傳遞。
[a, &b] 將a按值進行傳遞,b按引用進行傳遞。
[=,&a, &b] 除a和b按引用進行傳遞外,其他參數都按值進行傳遞。
[&, a, b] 除a和b按值進行傳遞外,其他參數都按引用進行傳遞。
(操作符重載函數參數),例如(int a,int &b)標識重載的()操作符的參數,沒有參數時,這部分可以省略。參數可以通過按值(如:(a,b))和按引用(如:(&a,&b))兩種方式進行傳遞
mutable與exception聲明,例如 mutable throw(),可省略
按值傳遞函數對象參數時,加上mutable修飾符后,可以修改按值傳遞進來的拷貝(注意是能修改拷貝,而不是值本身,如果沒有添加mutable,相當于對函數參數的增加了const修飾,無法修改參數)。exception聲明用于指定函數拋出的異常,如拋出整數類型的異常,可以使用throw(int)。
示例:
->返回值類型,例如 ->int 表示返回 int類型
標識函數返回值的類型,當返回值為void,或者函數體中只有一處return的地方(此時編譯器可以自動推斷出返回值類型)時,這部分可以省略。
{函數體},例如{cout<<“abc”;},不可省略,可以為空
// 無函數對象參數,輸出:1 2 { for_each(vctTemp.begin(), vctTemp.end(), [](int v){ cout << v << endl; }); }
// 以值方式傳遞作用域內所有可見的局部變量(包括this),輸出:11 12 { int a = 10; for_each(vctTemp.begin(), vctTemp.end(), [=](int v){ cout << v+a << endl; }); }
// 以引用方式傳遞作用域內所有可見的局部變量(包括this),輸出:11 13 12 { int a = 10; for_each(vctTemp.begin(), vctTemp.end(), [&](int v)mutable{ cout << v+a << endl; a++; }); cout << a << endl; }
// 以值方式傳遞局部變量a,輸出:11 13 10 { int a = 10; for_each(vctTemp.begin(), vctTemp.end(), [a](int v)mutable{ cout << v+a << endl; a++; }); cout << a << endl; }
// 以引用方式傳遞局部變量a,輸出:11 13 12 { int a = 10; for_each(vctTemp.begin(), vctTemp.end(), [&a](int v){ cout << v+a << endl; a++; }); cout << a << endl; }
// 傳遞this,輸出:21 22 { for_each(vctTemp.begin(), vctTemp.end(), [this](int v){ cout << v+m_nData << endl; }); }
// 除b按引用傳遞外,其他均按值傳遞,輸出:11 12 17 { int a = 10; int b = 15; for_each(vctTemp.begin(), vctTemp.end(), [=, &b](int v){ cout << v+a << endl; b++; }); cout << b << endl; }
int temp = 10;
vector<int> ivec = {30, -10, -20, 50, 40 ,100, -50};
std::sort(ivec.begin(), ivec.end(), [](const int &x, const int &y) {return abs(x) < abs(y);});
std::for_each(ivec.begin(), ivec.end(), [&](int &x) { x += temp; cout << x << endl;});
三、注意事項:
比較上面三種方式,有一些細節需要注意:
1. closure的狀態特指其運行的上下文。 closure將存貯它運行時需要的上下文,從而保證在closure創建時的上下文可以在closure運行時依然有效。
比如round就是closure的上下文。保存上下文的這一特點通常被稱作“capture”或者是"bind"。 capture可以自己寫,比如MyFuctor f(round); 也可以用boost::bind。
當然最方便的還是讓編譯器幫你自動完成。編譯器將自動識別closure用到的變量,然后創建一個匿名的類,將這個變量保存到匿名類的成員變量中。
C++中有兩種capture方式,by value和by reference。寫法是[=]和[&]。
需要注意的是,capture by reference是不會修改被capture變量的生命周期的,你要保證被capture的變量在closure運行時是有效的。
這一點不像Java,Java中變量被capture的話,就變成被引用了,從而GC不會回收它。
2. closure的類型是隱藏的,每次創建一個closure,編譯器都會創建一個新的類型。
如果你想保存一個clousre時就不是那么直接,因為你不知道它的類型。這時那需要一些模板技巧,可參考boost::function的實現。
簡單的方式是直接用std::function來保存。
std::function<int(float)> closure;
closure = [](float f) { return 0.0f };
closure = [](float f) { return 1.0f };
1. closure的狀態特指其運行的上下文。 closure將存貯它運行時需要的上下文,從而保證在closure創建時的上下文可以在closure運行時依然有效。
比如round就是closure的上下文。保存上下文的這一特點通常被稱作“capture”或者是"bind"。 capture可以自己寫,比如MyFuctor f(round); 也可以用boost::bind。
當然最方便的還是讓編譯器幫你自動完成。編譯器將自動識別closure用到的變量,然后創建一個匿名的類,將這個變量保存到匿名類的成員變量中。
C++中有兩種capture方式,by value和by reference。寫法是[=]和[&]。
需要注意的是,capture by reference是不會修改被capture變量的生命周期的,你要保證被capture的變量在closure運行時是有效的。
這一點不像Java,Java中變量被capture的話,就變成被引用了,從而GC不會回收它。
2. closure的類型是隱藏的,每次創建一個closure,編譯器都會創建一個新的類型。
如果你想保存一個clousre時就不是那么直接,因為你不知道它的類型。這時那需要一些模板技巧,可參考boost::function的實現。
簡單的方式是直接用std::function來保存。
std::function<int(float)> closure;
closure = [](float f) { return 0.0f };
closure = [](float f) { return 1.0f };
四、閉包(匿名函數)用處,可以是流程更清晰,易于理解,一般不能單獨使用,必須有上下文,閉包里處理的是上下文中的一些變量。一般情況下不能單獨使用
auto Do=[&]()
{
}
auto nextDo=[=](){
}
{
}
auto nextDo=[=](){
}
posted on 2018-05-27 18:17 Benjamin 閱讀(803) 評論(0) 編輯 收藏 引用 所屬分類: C/C++