閉包有很多種定義,一種說法是,閉包是帶有上下文的函數。說白了,就是有狀態的函數。更直接一些,不就是個類嗎?換了個名字而已。
一個函數, 帶上了一個狀態, 就變成了閉包了. 什么叫 "帶上狀態" 呢? 意思是這個閉包有屬於自己的變量, 這些個變量的值是創建閉包的時候設置的, 並在調用閉包的時候, 可以訪問這些變量.
函數是代碼, 狀態是一組變量 ,將代碼和一組變量捆綁 (bind) , 就形成了閉包 ,內部包含 static 變量的函數, 不是閉包, 因為這個 static 變量不能捆綁. 你不能捆綁不同的 static 變量. 這個在編譯的時候已經確定了.
閉包的狀態捆綁, 必須發生在運行時.
閉包的實現
C++ 里使用閉包有3個辦法
重載 operator()
因為閉包是一個函數+一個狀態, 這個狀態通過 隱含的 this 指針傳入. 所以 閉包必然是一個函數對象. 因為成員變量就是極好的用於保存狀態的工具, 因此實現 operator() 運算符重載, 該類的對象就能作為閉包使用. 默認傳入的 this 指針提供了訪問成員變量的途徑.
事實上, lambda 和 bind 的原理都是這個.
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);
lambda
c++11 里提供的 lambda表達式就是很好的語法糖. 其本質和手寫的函數對象沒有區別.
float round = 0.5; auto f = [=](float f) { return f + round; }
boost::bind/std::bind
標准庫提供的 bind 是更加強大的語法糖, 將手寫需要很多很多代碼的閉包, 濃縮到一行 bind 就可以搞定了.
int boost_func(float f, float round) { return f + round; } float round = 0.5; boost::function<int(float)> f = boost::bind(boost_func, _1, round);
比較上面三種方式,有一些細節需要注意:
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 };