最近看到某位網友的問題:「template 的函數的指標要怎麼傳給 template的函數?」由於沒有額外的說明,所以我不太確定他遇到的困難點在哪裡。
先來看看熱身題,假如存在下面程式片斷,要如何傳遞 Output1() 給 Call() 呢?:
template <class T>
void Output1(const T& obj)
{
cout << obj << endl;
}
template <class Fun, class T>
void Call(Fun f, const T& obj)
{
f(obj);
}
我想對熟悉 C++ 的人來說,答案應該非常簡單。只要像這樣即可:
Call(&Output1<int>, 12345);
Call(&Output1<const char*>, "goodbye world.");
這裡唯一比較重要的觀念是 template 並非真正的程式碼,必須要實體化之後編譯器才會產生真實的 function 或 class。更具體的說,Output1<int> 和 Output1<const char*> 都是真實存在的函數,但 Output1 並不是,它只是個模板。所以像下面這種寫法是錯誤的
Call(&Output1, 12345);
因為在還沒實體化之前函數並不真正存在,當然也沒有位址,編譯器根本不知道該傳遞甚麼東西給 Call。這也告訴我們,儘管 Output1 可以接受任何型別,但一旦我們想得到函數指標,Output1 就必須綁定到特定的型別上,所以不再是泛型。
但有的時候我們想傳遞的並不是某個特定的函數指標,而是這個泛用的功能,我猜網友原先的問題在這裡。
對純函數而言,這個問題大概無解,因為我們沒有其它的方式可以指涉一個 function template。但如果放寬標準到所有「像函數的東西」,那麼還是大有可為。最直接的觀察,原問題卡在傳遞函數指標之前必須要具現化,而造成型別綁定。如果能夠將這個綁定推遲到呼叫的時候,那麼問題就解決了。
struct Output2
{
template <class T>
void operator()(const T& obj)
{
cout << obj << endl;
}
};
template <class Fun, class T>
void Call(Fun f, const T& obj)
{
f("header --- ");
f(obj);
}
.....
Call(Output2(), 12345);
這個方法還可以得到另一個額外的好處,如果函數本身可以被inline的話,那麼最佳化之後就不會有函數呼叫的overhead。反之透過函數指標則無論如何都避不了間接呼叫的開銷,即使函數加了 inline 修飾也沒用。
對於單純「像函數的東西」這大概是最簡單的方案了,更複雜的用途可能必須依靠 template template parameter 或者 rebinding 來解決。篇幅有限,有機會再介紹。
留言列表