2017年1月8日

簡介 std::function (C++11 後的新功能)

std::function 是個 C++11 引入的新東西,作用有點類似 C 的 function pointer,不過為了考量 C++11 引入的新功能,效果上更加的一般化,使用範圍可以比 function pointer 更廣泛。
舉個例子:function pointer 通常是設計上為了引入 callback function 常用的技巧,以 C 來說, qsort 就是個不錯的例子。
因為對於 qsort 來說,兩個物件怎麼比大小不重要,他只需要知道比完後誰應該要排在前面就好,因此 qsort 的最後一項參數是 compare function,其形態是個 function pointer,此時 client code 只需要依據自己的需求在 compare function 定義好 2 個物件的先後順序,之後把 compare function 丟給 qsort 就好。

類似的技巧在 C++ 還有 template 可以用,在 C++ 因為 class 可以自行多載 (overloading) operator(),因此讓 class object 具有 function 特性也是可能的,這種物件也被叫做 Functor,可以參考這篇這篇有比較詳細的說明。

C++11 後因為引入了 lambda function,並且 lambda function 在本質上其實不是個 function,這也導致 C++11 後所謂的可被呼叫的物件 (callable object) 被進一步的擴充了,這下問題就變得複雜了:
  1. function pointer 不能指向 lambda function,因為 lambda function 本質上不是 function
  2. static member function 跟 non-static member function 形態上不同的,原因在於 non-static member function 會綁訂一個物件
  3. Functor 跟上面所說的又是完全不同的存在,所以型態上也是不同的
因為上述的原因,在早期 C++ 使用 template 的解法是最常見的,像是 std::sort (請上面 functor 那兩篇) 就是其中之一。然後 template 是個在編譯時期才根據需求產生實體的玩意兒,換言之採用 template 的解法最大缺點就是你的 header 會變得很大坨,同時編譯時間也會變久 (看看那精美的 boost...),當然這在有 std::function 前已經是個很好的解法了。而 std::function 大概就是為了解決這個問題才另外設計出來的東西

參考 C++ reference 上的說明,我們可以看到 std::function 簡單來說就像是個介面,首先定義好他能接受的可呼叫物件 (callable object) 的長相 (signature,也就是 function 的回傳型態 (return type) 與參數型態 (parameter type)),利用 std::function 就可以把能符合這長相的儲存起來。對,就是儲存,所以也能把兩個 std::function 的接住的內容交換。

所謂的能符合其實範圍滿廣的,他並不需要完全一致,只要能藉由轉型變成相同型態那就可以了。所以像是 std::function<void (int)> 也能儲存一個 void(double) 這種 signature 的 callable object。所以像是下面這段 code 是合法的:

int add(int a, int b) { return (a + b); }
int sub(int a, int b) { return (a - b); }
int mul(int a, int b) { return (a * b); }
double divide(double a, double b) { return (a / b); }

int compute(int a, int b, std::function op) { return op(a, b); }

int main()
{
 compute(1, 2, add);
 compute(1, 2, sub);
 compute(1, 2, mul);
 compute(1, 2, divide);
 return 0;
}

當然,fuctor 或是 lambda expresion 也是可以的,所以下面這也是合法的

compute(1, 2, [](int a, int b) -> int { return (a % b); });

non-static member function 因為需要綁定物件,所以要先用 std::bind 處理過,所以會比較麻煩一點:

class Math
{
public:
 int mod(int a, int b) { return (a % b); }
};

Math math;
compute(1, 2, std::bind(&Math::mod, &math, std::placeholders::_1, std::placehoders::_2));

就像上面的這些例子,std::function 因為利用了 type erasure 的技巧,所以他可以應用的範圍相當廣泛,而且也沒有 template 必須放在 header 的限制,因此就實用性上來說是個相當不錯的選擇。在 stackoverflow 上也是有些人針對這幾種方法做比較後的結論是 "盡量採用 std::function"。

當然 std::function 並非沒有缺點,首先他會有些許的 overhead (可以參考一些別人寫的實作方法);另一方面他對於型態的限制其實很鬆,只要轉型後能匹配的都可以,因此內含的隱式型態轉換是很容易藏 bug 的,使用時如不慎容易被搞 = =

就我個人的結論來說,std::function 跟 C++11 引入的許多新功能一樣,具有非常好又強的特性,但是使用上藏了一些細節可能導致非預期行為或者是編譯出事 (對,又是那個隱式型態轉換,當扯上 overloading 時還有 SFINAE 原則後實在很複雜難解...),如果是要用在 API 或是 interface 設計的話還是先多方研究後再動手使用會比較保險。

沒有留言:

張貼留言