《深度探索C++物件模型》讀書筆記(4)

來源:互聯網
上載者:User
《深度探索C++物件模型》讀書筆記(4)。

  ***非靜態成員函數(Nonstatic Member Functions)***

   C++的設計準則之一就是:nonstatic member function至少必須和一般的nonmember function有相同的效率。也就是說,如果我們要在以下兩個函數之間作選擇:

 float magnitude3d(const Point3d *this) { ... }
float Point3d::magnitude3d() const { ... }

   那麼選擇member function不應該帶來什麼額外負擔。因為編譯器內部已將“member函數實體”轉化為對等的“nonmember函數實體”。下面是magnitude()的一個nonmember定義:

 float Pointer3d::magnitude() const
{
return sqrt(_x*_x + _y*_y + _z*_z);
}
// 內部轉化為
float magnitude_7Point3dFv(const Point3d *this)  //已對函數名稱進行“mangling”處理
{
return sqrt(this->_x*this->_x + this->_y*this->_y + this->_z*this->_z);
}

   現在,對該函數的每一個叫用作業也都必須轉換:

 obj.magnitude();
// 轉換為
magnitude_7Point3dFv(&obj);

   對於class中的memeber,只需在member的名稱中加上class名稱,即可形成獨一無二的命名。但由於member function可以被重載化,所以需要更廣泛的mangling手法,以提供絕對獨一無二的名稱。其中一種做法就是將它們的參數鏈表中各參數的類型也編碼進去。

 class Point {
public:
void x(float newX);
float x();
...
};
// 內部轉化為
class Point {
void x_5PointFf(float newX);  // F表示function,f表示其第一個參數類型是float
float x_5PointFv();  // v表示其沒有參數
};

關鍵字: malloc wxWidgets OpenGL 多態性 doxygen 《深度探索C++物件模型》讀書筆記(4)。

   上述的mangling手法可在連結時期檢查出任何不正確的叫用作業,但由於編碼時未考慮傳回型別,故如果傳回型別聲明錯誤,就無法檢查出來。

   ***虛擬成員函數(Virtual Member Functions)***

   對於那些不支援多態的對象,經由一個class object調用一個virtual function,這種操作應該總是被編譯器像對待一般的nonstatic member function一樣地加以決議:

 // Point3d obj
obj.normalize();
// 不會轉化為
(*obj.vptr[1])(&obj);
// 而會被轉化未
normalize_7Point3dFv(&obj);

   ***靜態成員函數(Static Member Functions)***

   在引入static member functions之前,C++要求所有的member functions都必須經由該class的object來調用。而實際上,如果沒有任何一個nonstatic data members被直接存取,事實上就沒有必要通過一個class object來調用一個member function.這樣一來便產生了一個矛盾:一方面,將static data member聲明為nonpublic是一種好的習慣,但這也要求其必須提供一個或多個member functions來存取該member;另一方面,雖然你可以不靠class object來存取一個static member,但其存取函數卻得綁定於class object之上。

   static member functions正是在這種情形下應運而生的。

   編譯器的開發人員針對static member functions,分別從編譯層面和語言層面對其進行了支援:

   (1)編譯層面:當class設計者希望支援“沒有class object存在”的情況時,可把0強制轉型為一個class指標,因而提供出一個this指標實體:

 // 函數調用的內部轉換
object_count((Point3d*)0);

關鍵字: malloc wxWidgets OpenGL 多態性 doxygen 《深度探索C++物件模型》讀書筆記(4)。

   (2)語言層面:static member function的最大特點是沒有this指標,如果取一個static member function的地址,獲得的將是其在記憶體中的位置,其地址類型並不是一個“指向class member function的指標”,而是一個“nonmember函數指標”:

 unsigned int Point3d::object_count() { return _object_count; }
&Point3d::object_count();
// 會得到一個地址,其類型不是
unsigned int (Point3d::*)();
// 而是
unsigned int (*)();

   static member function經常被用作回調(callback)函數。

   ***虛擬成員函數(Virtual Member Functions)***

   對於像ptr->z()的叫用作業將需要ptr在執行期的某些相關資訊,為了使得其能在執行期順利高效地找到並調用z()的適當實體,我們考慮往對象中添加一些額外資訊。

   (1)一個字串或數字,表示class的類型;

   (2)一個指標,指向某表格,表格中帶有程式的virtual functions的執行期地址;

   在C++中,virtual functions可在編譯時間期獲知,由於程式執行時,表格的大小和內容都不會改變,所以該表格的建構和存取皆可由編譯器完全掌握,不需要執行期的任何介入。

   (3)為了找到表格,每一個class object被安插上一個由編譯器內部產生的指標,指向該表格;

   (4)為了找到函數地址,每一個virtual function被指派一個表格索引值。

   一個class只會有一個virtual table,其中內含其對應的class object中所有active virtual functions函數實體的地址,具體包括:

   (a)這個class所定義的函數實體

   它會改寫一個可能存在的base class virtual function函數實體。若base class中不存在相應的函數,則會在derived class的virtual table增加相應的slot.

關鍵字: malloc wxWidgets OpenGL 多態性 doxygen 《深度探索C++物件模型》讀書筆記(4)。

   (b)繼承自base class的函數實體

   這是在derived class決定不改寫virtual function時才會出現的情況。具體來說,base class中的函數實體的地址會被拷貝到derived class的virtual table相對應的slot之中。

   (c)pure_virtual_called函數實體

   對於這樣的式子:

 ptr->z();

   運用了上述手法後,雖然我不知道哪一個z()函數實體會被調用,但卻知道每一個z()函數都被放在slot 4(這裡假設base class中z()是第四個聲明的virtual function)。

 // 內部轉化為
(*ptr->vptr[4])(ptr);

   ***多重繼承下的Virtual Functions***

   在多重繼承中支援virtual functions,其複雜度圍繞在第二個及後繼的base classes身上,以及“必須在執行期調整this指標”這一點。

   多重繼承到來的問題:

   (1)經由指向“第二或後繼之base class”的指標(或reference)來調用derived class virtual function,該叫用作業連帶的“必要的this指標調整”操作,必須在執行期完成;

   以下面的繼承體係為例:

 class Base1 {
public:
Base1();
virtual ~Base1();
virtual void speakClearly();
virtual Base1 *clone() const;
protected:
float data_Base1;
};

class Base2 {
public:
Base2();
virtual ~Base2();
virtual void mumble();
virtual Base2 *clone() const;
protected:
float data_Base2;
};

class Derived : public Base1, public Base2 {
public:
Derived();
virtual ~Derived();
virtual Derived *clone() const;
protected:
float data_Derived;
};

關鍵字: malloc wxWidgets OpenGL 多態性 doxygen 《深度探索C++物件模型》讀書筆記(4)。

   對於下面一行:

 Base2 *pbase2 = new Derived;

   會被內部轉化為:

 // 轉移以支援第二個base class
Derived *temp = new Derived;
Base2 *pbase2 = temp ? temp + sizeof(Base1) : 0;

   如果沒有這樣的調整,指標的任何“非多態運用”都將失敗:

 pbase2->data_Base2;

   當程式員要刪除pbase2所指的對象時:

 // 必須調用正確的virtual destructor函數實體
// pbase2需要調整,以指出完整對象的起始點
delete pbase2;

   指標必須被再一次調整,以求再一次指向Derived對象的起始處。然而上述的offset加法卻不能夠在編譯時間期直接設定,因為pbase2所指的真正對象只有在執行期才能確定。

   自此,我們明白了在多重繼承下所面臨的獨特問題:經由指向“第二或後繼之base class”的指標(或reference)來調用derived class virtual function,該叫用作業所連帶的“必要的this指標調整”操作,必須在執行期完成。有兩種方法來解決這個問題:

   (a)將virtual table加大,每一個virtual table slot不再只是一個指標,而是一個彙總體,內含可能的offset以及地址。這樣一來,virtual function的叫用作業發生改變:

 (*pbase2->vptr[1])(pbase2);
// 改變為
(*pbase2->vptr[1].faddr)(pbase2 + pbase2->vptr[1].offset);

   這個做法的缺點是,它相當於連帶處罰了所有的virtual function叫用作業,不管它們是否需要offset的調整。

   (b)利用所謂的thunk(一小段assembly碼),其做了以下兩方面工作:

關鍵字: malloc wxWidgets OpenGL 多態性 doxygen 《深度探索C++物件模型》讀書筆記(4)。

   (1)以適當的offset值調整this指標;

   (2)跳到virtual function去。

 pbase2_dtor_thunk:
this += sizeof(base1);
Derived::~Derived(this);

   Thunk技術允許virtual table slot繼續內含一個簡單的指標,slot中的地址可以直接指向virtual function,也可以指向一個相關的thunk.於是,對於那些不需要調整this指標的virtual function而言,也就不需要承載效率上的額外負擔。

   (2)由於兩種不同的可能:

   (a)經由derived class(或第一個base class)調用;

   (b)經由第二個(或其後繼)base class調用,同一函數在virtual table中可能需要多筆對應的slot;

 Base1 *pbase1 = new Derived;
Base2 *pbase2 = new Derived;

delete pbase1;
delete pbase2;

   雖然兩個delete操作導致相同的Derived destructor,但它們需要兩個不同的virtual table slots:

   (a)pbase1不需要調整this指標,其virtual table slot需放置真正的destructor地址

   (b)pbase2需要調整this指標,其virtual table slot需要相關的thunk地址

   具體的解決方案是:

   在多重繼承下,一個derived class內含n-1個額外的virtual tables,n表示其上一層base classes的數目。按此手法,Derived將內含以下兩個tables:vtbl_Derived和vtbl_Base2_Derived.

   (3)允許一個virtual function的傳回值類型有所變化,可能是base type,可能是publicly derived type,這一點可以通過Derived::clone()函數實體來說明。

 Base2 *pb1 = new Derived;

// 調用Derived::clone()
// 傳回值必須被調整,以指向Base2 subobject
Base2 *pb2 = pb1->clone();

關鍵字: malloc wxWidgets OpenGL 多態性 doxygen 《深度探索C++物件模型》讀書筆記(4)。

   當運行pb1->clone()時,pb1會被調整指向Derived對象的起始地址,於是clone()的Derived版會被調用:它會傳回一個指標,指向一個新的Derived對象;該對象的地址在被指定給pb2之前,必須先經過調整,以指向Base2 subobject.當函數被認為“足夠小”的時候,Sun編譯器會提供一個所謂的“split functions”技術:以相同演算法產生出兩個函數,其中第二個在返回之前,為指標加上必要的offset,於是無論通過Base1指標或Derived指標調用函數,都不需要調整傳回值;而通過Base2指標所調用的,是另一個函數。

  ***虛擬繼承下的Virtual Functions***

   其內部機制實在太過詭異迷離,故在此略過。唯一的建議是:不要在一個virtual base class中聲明nonstatic data members.

   ***函數的效能***

   由於nonmember、static member和nonstatic member函數都被轉化為完全相同的形式,故三者的效率安全相同。virtual member的效率明顯低於前三者,其原因有兩個方面:(a)建構函式中對vptr的設定操作;(b)位移差值模型。

   ***指向Member Function的指標***

   取一個nonstatic member function的地址,如果該函數是nonvirtual,則得到的結果是它在記憶體中真正的地址。

   我們可以這樣定義並初始化該指標:

 double (Point::*coord)() = &Point::x;

   想調用它,可以這麼做:

 (origin.*coord)();
 (ptr->*coord)();

   “指向Virtual Member Functions”之指標將會帶來新的問題,請注意下面的程式片段:

 float (Point::*pmf)() = &Point::z;
Point *ptr = new Point3d;

關鍵字: malloc wxWidgets OpenGL 多態性 doxygen 《深度探索C++物件模型》讀書筆記(4)。

   其中,pmf是一個指向member function的指標,被設值為Point::z()(一個virtual function)的地址,ptr則被指向一個Point3d對象。

   如果我們直接經由ptr調用z():

ptr->z();  // 調用的是Point3d::z()

   但如果我們經由pmf間接調用z():

 (ptr->*pmf)();  // 仍然調用的是Point3d::z()

   也就是說,虛擬機器制仍然能夠在使用“指向member function之指標”的情況下運行,但問題是如何?呢?

   對一個nonstatic member function取其地址,將獲得該函數在記憶體中的地址;而對一個virtual member function取其地址,所能獲得的只是virtual function在其相關之virtual table中的索引值。因此通過pmf來調用z(),會被內部轉化為以下形式:

 (*ptr->vptr[(int)pmf])(ptr);

   但是我們如何來判斷傳給pmf的函數指標指向的是記憶體位址還是virtual table中的索引值呢?例如以下兩個函數都可指定給pmf:

 // 二者都可以指定給pmf
float Point::x() { return _x; }  // nonvirtual函數,代表記憶體位址
float Point::z() { return 0; }  // virtual函數,代表virtual table中的索引值

   cfront 2.0是通過判斷該值的大小進行判斷的(這種實現技巧必須假設繼承體系中最多隻有128個virtual functions)。

   為了讓指向member functions的指標也能夠支援多重繼承和虛擬繼承,Stroustrup設計了下面一個結構體:

 // 用以支援在多重繼承之下指向member functions的指標
struct _mptr {
int delta;
int index;
union {
ptrtofunc faddr;
int v_offset;
};
};

關鍵字: malloc wxWidgets OpenGL 多態性 doxygen 《深度探索C++物件模型》讀書筆記(4)。

   其中,index表示virtual table索引,faddr表示nonvirtual member function地址(當index不指向virtual table時,被設為-1)。

   在該模型之下,以下叫用作業會被轉化為:

 (ptr->*pmf)();
// 內部轉化為
(pmf.index < 0)
? (*pmf.faddr)(ptr)  // nonvirtual invocation
: (*ptr->vptr[pmf.index](ptr)  // virtual invocation

   對於如下的函數調用:

 (pA.*pmf)(pB);  // pA、pB均是Point3d對象

   會被轉化成:

 pmf.iindex < 0
? (*pmf.faddr)(&pA + pmf.delta, pB)
: (*pA._vptr_Point3d[pmf.index].faddr)(&pA + pA._vptr_Point3d[pmf.index] + delta, pB);

  ***Inline Functions***

   在inline擴充期間,每一個形式參數都會被對應的實際參數取代。但是需要注意的是,這種取代並不是簡單的一一取代(因為這將導致對於實際參數的多次求值操作),而通常都需要引入臨時性對象。換句話說,如果實際參數是一個常量運算式,我們可以在替換之前先完成其求值操作;後繼的inline替換,就可以把常量直接綁上去。

   舉個例子,假設我們有以下簡單的inline函數:

 inline int min(int i, int j)
{
return i < j ? i : j;
}

   對於以下三個inline函數調用:

 minval = min(val1,val2);
minval = min(1024,2048);
minval = min(foo(),bar()+1);

   會分別被擴充為:

 minval = val1 < val2 ? val1 : val2;  // 參數直接代換
minval = 1024;  // 代換之後,直接使用常量
int t1;
int t2;
minval = (t1 = foo()), (t2 = bar()+1),t1 < t2 ? t1 : t2;  //有副作用,所以匯入臨時對象

關鍵字: malloc wxWidgets OpenGL 多態性 doxygen 《深度探索C++物件模型》讀書筆記(4)。

   inline函數中的局部變數,也會導致大量臨時性對象的產生。

 inline int min(int i, int j)
{
int minval = i < j ? i : j;
return minval;
}

   則以下運算式:

 minval = min(val1, val2);

   將被轉化為:

 int _min_lv_minval;
minval = (_min_lv_minval = val1 < val2 ? val1 : val2),_min_lv_minval;

   總而言之,inline函數中的局部變數,再加上有副作用的參數,可能會導致大量臨時性對象的產生。特別是如果它以單一運算式被擴充多次的話。新的Derived對象的地址必須調整,以指向其Base2 subobject.

  系列文章:

  《深度探索C++物件模型》讀書筆記(1)

  《深度探索C++物件模型》讀書筆記(2)

  《深度探索C++物件模型》讀書筆記(3)

  《深度探索C++物件模型》讀書筆記(5)

  《深度探索C++物件模型》讀書筆記(6)

  《深度探索C++物件模型》讀書筆記(7)

  《深度探索C++物件模型》讀書筆記 最後一記

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.