C++物件模型——指向Member Function的指標 (Pointer-to-Member Functions)(第四章)

來源:互聯網
上載者:User

標籤:

4.4 指向Member Function的指標 (Pointer-to-Member Functions) 取一個nonstatic data member的地址,得到的結果是該member在 class 布局中的byte位置(再加1),它是一個不完整的值,需要被綁定於某個 class object的地址上,才能夠被存取.
取一個nonstatic member function的地址,如果該函數是nonvirtual,則得到的結果是它在記憶體中真正的地址.然而這個值也是不完全的,它也需要被綁定與某個 class object的地址上,才能夠通過它調用該函數,所有的nonstatic member functions都需要對象的地址(以參數 this 指出).
一個指向member function的指標,其聲明文法如下:
double// return type(Point::*// class the function is member pmf)// name of the pointer to member();// argument list
然後可以這樣定義並初始化該指標:
double (Point::*coord)() = &Point::x;
也可以這樣指定值:
coord = &Point::y;
想調用它,可以這樣做:
(origin.*coord)();

(ptr->*coord)();
這些操作會被編譯器轉化為:
(coord)(&origin);

(coord)(ptr);
指向member function的指標的聲明文法,以及指向"member selection運算子"的指標,其作用是作為 this 指標的空間保留者.這這也 就是為什麼 static member function(沒有 this 指標)的類型是"函數指標",而不是"指向member function的指標"的原因.
使用一個"member function指標",如果並不用於 virtual function,多重繼承,virtual base class 等情況的話,並不會比使用一個"nonmember function指標"的成本更高.上述三種情況對於"member function指標"的類型以及調用都太過複雜.事實上,對於那些沒有 virtual functions或 virtual base class,或multiple base class 的 class 而言,編譯器可以為它們提供相同的效率.下一節討論為什麼 virtual function的出現,會使得"member function指標"更複雜化.

支援"指向Virtual Member Functions"的指標注意下面的程式片段:
float (Point::*pmf)() = &Point::z;Point *ptr = new Point3d;
pmf,一個指向member function的指標,被設值為Point::z()(一個 virtual function)的地址,ptr則被指定以一個Point3d對象,如果直接經由ptr調用z():
ptr->z();
則被調用的是point3d::z(),但如果從pmf間接調用z()呢?
(ptr->pmf)();
仍然是Point3d::z()被調用嗎?也就是說, 虛擬機器制仍然能夠在使用"指向member function的指標"的情況下運行嗎?答案是yes,問題是如何?呢?
對一個nonstatic member function取其地址,將獲得該函數在記憶體中的地址,然而面對一個 virtual function,其地址在編譯時間期是未知的,所能直到的僅是 virtual function在其相關的 virtual table中的索引值.也就是說,對一個 virtual member function取其地址,所能獲得的只是一個索引值.
例如,假設有以下的Point聲明:
class Point {public:virtual ~Point();float x();float y();virtual float z();};
然而取得destructor的地址:
&Point::~Point;
得到的結果是1,取x()或y()的地址:
&Point::x();&Point::y();
得到的則是函數在記憶體中的地址,因為它們不是 virtual,取z()的地址:
&Point::z();
得到的結果是2,通過pmf來調用z(),會被內部轉化為一個編譯時間期的式子,一般形式如下:
(*ptr->vptr[(int)pmf])(ptr);
對一個"指向member function的指標"評估求值(evaluted),會因為該值有兩種意義而複雜化;其叫用作業也將有別於常規叫用作業.pmf的內部定義,也就是:
float (Point::*pmf)();
必須允許該函數能夠定址出nonvirtual x()和 virtual z()兩個member functions,而那兩個函數有著相同的原型:
// 二者都可以被指定給pmffloat Point::x() { return _x; }float Point::z() { return 0; }
只不過 其中一個代表記憶體位址,另一個代表 virtual table中的索引值.因此,編譯器必須定義pmf使它能夠(1)還有兩種數值,(2)更重要的是其數值可以被區別代表記憶體位址還是 virtual table中的索引值.

在多重繼承下,指向Member Functions的指標 為了讓指向member functions的指標也能夠支援多重繼承和虛擬繼承,Stroustrup設計了下面一個結構體:
// 一般結構,用以支援在多重繼承下指向member functions的指標struct __mptr {int delta;int index;union {ptrtofunc faddr;int v_offset;};};
它們要表現什麼呢? index和faddr分別(不同時)帶有 virtual table索引和nonvirtual member function地址.在該模型下,像這樣的叫用作業:
(ptr->*pmf)();
會變成:
(pmf.index < 0)?// non-virtual invocation(*pmf.faddr)(ptr):// virtual invocation(*ptr->vptr[pmf.index](ptr));
這種方法所受到的批評是,每一個叫用作業都得付出上述成本,檢查其是否為 virtual 或 nonvirtual.Microsoft把這項檢查拿掉,匯入一個它所謂的vcall thunk.在此策略下,faddr被指定的要不就是真正的member function地址(如果函數是nonvirtual的話),要不就是vcall thunk的地址.於是 virtual 或novirtual函數叫用作業透明化,vcall thunk會選出並調用相關 virtual table 中的適當slot.
這個結構體的另一個副作用是,當傳遞一個不變的值的指標給member function時,它需要產生一個臨時性對象.舉個例子,如果這樣做:
extern Point3d foo(const Point3ed&, Point3d(Point3d::*)());void bar(const Point3d& p) {Point3d pt = foo(p, &Point3d::normal);}
其中&Point3d::normal的值類似這樣:
{0, -1, 10727417}
將需要產生一個臨時性對象,有明確的初值:
__mptr temp = {0, -1, 10727417}foo(p, temp);
本節一開始的那個結構體,delta欄位表示 this 指標的offset值.而v_offset欄位放的是一個 virtual(或多重繼承中的第二或後繼的)base class 的vptr位置.如果vptr被編譯器放在 class 對象的起始處,這個欄位就沒有必要了,代價則是C對象相容性降低.這些欄位只在多重繼承或虛擬繼承的情況下才有其必要性,有許多編譯器在自身內部根據不同的 class 特性提供多種指向member functions的指標形式,例如Microsoft就供應了三種風格:
1. 一個單一繼承執行個體(其中帶有vcall thunk地址或是函數地址)
2. 一個多重繼承執行個體(其中帶有faddr和delta兩個members)
3. 一個虛擬繼承執行個體(其中帶有四個members)

"指向Member Functions的指標"的效率在下面一組測試中,cross_product()函數經由以下方式調用:
1. 一個指向 nonmember function 的指標
2. 一個指向 class member function 的指標
3. 一個指向 virtual member function 的指標
4. 多重繼承下的 nonvirtual 以及 virtual member function call
5. 虛擬繼承下的 nonvirtual 以及 virtual member function call.

第一個測試(指向 nonmember function 的指標)以下列方式進行:
Point3d* (*pf)(const Point3d&, const Point3d &) = cross_product;for (int iters = 0; iters < 10000000; iters++) (*pf)(pA, pB);return 0;
第二個測試 (指向 member function 的指標)的聲明和叫用作業如下:
Point3d* (Point3d::*pmf)(const Point3d &) const = &Point3d::cross_product;for (int iters = 0; iters < 10000000; iters++)(pA.*pmf)(pB);
上述操作會被轉化為以下的內部形式,於是以下的函數調用:
(pA.*pmf)(pB);
會被轉化為這樣的判斷:
pmf.index < 0? (*pmf.faddr)(&pA + pmf.delta, pB): (*pA.__vptr__Point3d[pmf.index].faddr)(&pA + pA.__vptr__Point3d[pmf.index].delta, pB);
一個"指向member function的指標"是一個結構,內含三個欄位:index,faddr和delta.index若不是內帶一個相關 virtual table的索引值,就是以-1表示函數是 nonvirtual.faddr帶有nonvirtual member function的地址.delta帶有一個可能的 this 指標調整.

著作權聲明:本文為博主原創文章,未經博主允許不得轉載。

C++物件模型——指向Member Function的指標 (Pointer-to-Member Functions)(第四章)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.