C++ 閉包(closure)


閉包有很多種定義,一種說法是,閉包是帶有上下文的函數。說白了,就是有狀態的函數。更直接一些,不就是個類嗎?換了個名字而已。

一個函數, 帶上了一個狀態, 就變成了閉包了. 什么叫 "帶上狀態" 呢? 意思是這個閉包有屬於自己的變量, 這些個變量的值是創建閉包的時候設置的, 並在調用閉包的時候, 可以訪問這些變量.

函數是代碼, 狀態是一組變量 ,將代碼和一組變量捆綁 (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 };


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM