轉載自:
http://www.stlchina.org/documents/EffectiveSTL/files/item_41.html
相關參考:http://www.cnblogs.com/oomusou/archive/2007/05/12/744000.html
ptr_fun/mem_fun/mem_fun_ref系列是什么意思的?有時候你必須使用這些函數,有時候不用,總之,它們是做什么的?它們似乎只是坐在那里,沒用地掛在函數名周圍就像不合身的外衣。它們不好輸入,影響閱讀,而且難以理解。這些東西是STL古董的附加例子(正如在條款10和18中描述的那樣),或者只是一些標準委員會的成員因為太閑和扭曲的幽默感而強加給我們的語法玩笑?
冷靜一下。雖然ptr_fun、mem_fun和mem_fun_ref的名字不太好聽,但它們做了很重要的工作,而不是語法玩笑,這些函數的主要任務之一是覆蓋C++固有的語法矛盾之一。
如果我有一個函數f和一個對象x,我希望在x上調用f,而且我在x的成員函數之外。C++給我三種不同的語法來實現這個調用:
f(x); // 語法#1:當f是一個非成員函數
x.f(); // 語法#2:當f是一個成員函數
// 而且x是一個對象或一個對象的引用
p->f(); // 語法#3:當f是一個成員函數
// 而且p是一個對象的指針
現在,假設我有一個可以測試Widget的函數,
void test(Widget& w); // 測試w,如果沒通過
// 就標記為“failed”
而且我有一個Widget的容器:
vector<Widget> vw; // vw容納Widget
要測試vw中的每個Widget,我很顯然可以這么使用for_each:
for_each(vw.begin(), vw.end(), test); // 調用#1(可以編譯)
但想象test是一個Widget的成員函數而不是一個非成員函數,也就是說,Widget支持自我測試:
class Widget {
public:
void test(); // 進行自我測試;如果沒通過
// 就把*this標記為“failed”
};
在一個完美的世界,我也將能使用for_each對vw中的每個對象調用Widget::test:
for_each(vw.begin(), vw.end(),
&Widget::test); // 調用#2(不能編譯)
實際上,如果世界真的完美,我將也可以使用for_each來在Widget*指針的容器上調用Widget::test:
list<Widget*> lpw; // lpw容納Widget的指針
for_each(lpw.begin(), lpw.end(),
&Widget::test); // 調用#3(也不能編譯)
但是想想在這個完美的世界里必須發生的。在調用#1的for_each函數內部,我們以一個對象為參數調用一個非成員函數,因此我們必須使用語法#1。在調用#2的for_each函數內部,我們必須使用語法#2,因為我們有一個對象和一個成員函數。而調用#3的for_each函數內部,我們需要使用語法#3,因為我們將面對一個成員函數和一個對象的指針。因此我們需要三個不同版本的for_each,而那個世界將有多完美?
在我們擁有的世界上,我們只有一個版本的for_each。想一種實現不難:
template<typename InputIterator, typename Function>
Function for_each(InputIterator begin, InputIterator end, Function f)
{
while (begin != end) f(*begin++);
}
這里,我強調當調用時for_each是用語法#1這個事實。這是STL里的一個普遍習慣:函數和函數對象總使用用于非成員函數的語法形式調用。這解釋了為什么調用#1可以編譯而調用#2和#3不。這是因為STL算法(包括for_each)牢牢地綁定在句法#1上,而且只有調用#1與那語法兼容。
也許現在清楚為什么mem_fun和mem_fun_ref存在了。它們讓成員函數(通常必須使用句法#2或者#3來調用的)使用句法1調用。
mem_fun和mem_fun_ref完成這個的方式很簡單,雖然如果你看一下這些函數之一的聲明會稍微清楚些。它們是真的函數模板,而且存在mem_fun和mem_fun_ref模板的幾個變體,對應于它們適配的不同的參數個數和常量性(或缺乏)的成員函數??匆粋€聲明就足以理解事情怎樣形成整體的:
template<typename R, typename C> // 用于不帶參數的non-const成員函數
mem_fun_t<R,C> // 的mem_fun聲明。
mem_fun(R(C::*pmf)()); // C是類,R是被指向
// 的成員函數的返回類型
mem_fun帶有一個到成員函數的指針,pmf,并返回一個mem_fun_t類型的對象。這是一個仿函數類,容納成員函數指針并提供一個operator(),它調用指向在傳給operator()的對象上的成員函數。例如,在這段代碼中,
list<Widget*> lpw; // 同上
...
for_each(lpw.begin(), lpw.end(),
mem_fun(&Widget::test)); // 這個現在可以編譯了
for_each接受一個mem_fun_t類型的對象,持有一個Widget::test的指針。對于在lpw里的每個Widget*指針,for_each使用語法#1“調用”mem_func_t,而那個對象立刻在Widget*指針上使用句法#3調用Widget::test。
總的來說,mem_fun適配語法#3——也就是當和Widget*指針配合時Widget::test要求的——到語法1,也就是for_each用的。因此也不奇怪像mem_fun_t這樣的類被稱為函數對象適配器。知道這個不應該使你驚訝,完全類似上述的,mem_fun_ref函數適配語法#2到語法#1,并產生mem_fun_ref_t類型的適配器對象。
mem_fun和mem_fun_ref產生的對象不僅允許STL組件假設所有函數都使用單一的語法調用。它們也提供重要的typedef,正如ptr_fun產生的對象一樣。條款40告訴了你這些typedef后面的故事,所以我將不在這里重復它。但是,這使我們能夠理解為什么這個調用可以編譯。
for_each(vw.begin(), vw.end(), test); // 同上,調用#1;
// 這個可以編譯
而這些不能:
for_each(vw.begin(), vw.end(), &Widget::test); // 同上,調用#2;
// 不能編譯
for_each(lpw.begin(), lpw.end(), &Widget::test); // 同上,調用#3;
// 不能編譯
第一個調用(調用#1)傳遞一個真的函數,因此用于for_each時不需要適配它的調用語法;這個算法將固有地使用適當的語法調用它。而且,for_each沒有使用ptr_fun增加的typedef,所以當把test傳給for_each時不必使用ptr_fun。另一方面,增加的typedef不會造成任何損傷,所以這和上面的調用做相同的事情:
for_each(vw.begin(), vw.end(), ptr_fun(test)); // 可以編譯,行為
// 就像上面的調用#1
如果你關于什么時候使用ptr_fun什么時候不使用而感到困惑,那就考慮每當你傳遞一個函數給STL組件時都使用它。STL將不在乎, 并且沒有運行期的懲罰??赡艹霈F的最壞的情況就是一些讀你代碼的人當看見不必要的ptr_fun使用時,可能會揚起眉毛。我認為,那有多讓你操心依賴于你對揚起眉毛的敏感性。
一個與ptr_fun有關的可選策略是只有當你被迫時才使用它。如果當typedef是必要時你忽略了它,你的編譯器將退回你的代碼。然后你得返回去添加它。
mem_fun和mem_fun_ref的情況則完全不同。只要你傳一個成員函數給STL組件,你就必須使用它們,因為,除了增加typedef(可能是或可能不是必須的)之外,它們把調用語法從一個通常用于成員函數的適配到在STL中到處使用的。當傳遞成員函數指針時如果你不使用它們,你的代碼將永遠不能編譯。
現在只留下成員函數適配器的名字,而在這里,最后,我們有一個真實的STL歷史產物。當對這些種適配器的需求開始變得明顯時,建立STL的人們正專注于指針的容器。(這種容器的缺點在條款7、20和33描述,這看起來可能很驚人,但是記住指針的容器支持多態,而對象的容器不支持。)他們需要用于成員函數的適配器,所以他們選擇了mem_fun。但很快他們意識到需要一個用于對象的容器的另一個適配器,所以他們使用了mem_fun_ref。是的,它非常不優雅,但是這些事情發生了。告訴我你從未給你的任何組件一個你過后意識到,呃,很難概括的名字……
posted on 2008-06-26 20:11
楊彬彬 閱讀(1106)
評論(0) 編輯 收藏 引用 所屬分類:
STL