亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

c++ 虛函數(shù) 工作機(jī)制 原理( virtual function )

系統(tǒng) 3145 0

/*
*晚上花了幾個(gè)小時(shí)翻譯了下,第一次翻譯這么長的文字;挺累呀,翻譯的很多地方也不算通順,權(quán)當(dāng)自娛自樂了。
*版權(quán)所有 xt2120#gmail 謝絕轉(zhuǎn)載
*/

c++ 虛函數(shù) 原理 機(jī)制 c 虛函數(shù)表 表指針

上個(gè)月,我介紹了虛擬函數(shù)。我概述了如何使用虛擬函數(shù)來實(shí)現(xiàn)一個(gè)設(shè)備無關(guān)的文件系統(tǒng),并詳細(xì)描述了如何創(chuàng)建一個(gè)具有多態(tài)行為的幾何圖形類。這個(gè)月我將繼續(xù)解釋虛擬函數(shù)的工作機(jī)制。首先,扼要重復(fù)一下其中的關(guān)鍵概念。

在c++中在基礎(chǔ)類和基類之間的公共繼承定義了一個(gè)is-a的關(guān)系。這就是說,給出了定義:
class D : public B {...}


D從B公共派生而來,所以任何D對(duì)象同時(shí)也是一個(gè)B對(duì)象。一個(gè)接收B對(duì)象指針或引用作為形式參數(shù)的函數(shù)也會(huì)允許一個(gè)指向D對(duì)象的指針(或引用,相應(yīng)的)作為參數(shù)。

更一般的情況,將一個(gè)指向D對(duì)象的指針轉(zhuǎn)換為一個(gè)指向B對(duì)象的指針是一個(gè)標(biāo)準(zhǔn)轉(zhuǎn)換而不需要cast。例如,如果d是一個(gè)類D的對(duì)象,B是D的公共繼承類,你可以寫下如下代碼:
B *pb = &d;

這會(huì)將&d(一個(gè) typde D*表達(dá)式)轉(zhuǎn)換為B*.將一個(gè)D對(duì)象綁定到B&上也是一個(gè)標(biāo)準(zhǔn)轉(zhuǎn)換(B & rb = d;)
which converts &d (an expression of type D * ) to B * . Binding a D object to a B & is also a standard conversion.

當(dāng)討論指向基類和繼承類對(duì)象的指針行為時(shí),它將能夠幫助你區(qū)分一個(gè)對(duì)象的動(dòng)態(tài)類型和靜態(tài)類型。一個(gè)對(duì)象的靜態(tài)類型是用于引用那個(gè)對(duì)象的表達(dá)式。一個(gè)對(duì)象的動(dòng)態(tài)類型是其“實(shí)際的”類型--當(dāng)指針被創(chuàng)建時(shí)對(duì)象的類型。

比如,使用上面定義和初始化的pb,*pb的靜態(tài)類型為B,但是其動(dòng)態(tài)類型為D(B* pb = &d;)。或者考慮:
B &rb = d;

rb的靜態(tài)類型為B同時(shí)其動(dòng)態(tài)類型為D。*pb的靜態(tài)類型總是B,但是其動(dòng)態(tài)類型在程序執(zhí)行的時(shí)候可能會(huì)發(fā)生變化。例如,如果b是一個(gè)B對(duì)象,那么
pb = &b;


會(huì)改變*pb的動(dòng)態(tài)類型為B。

一個(gè)派生類繼承了其基類的所有成員。一個(gè)派生類不能丟棄任何其所繼承的成員,但是它能夠使用覆寫(override)一個(gè)繼承的成員函數(shù)。

在c++中,一個(gè)非靜態(tài)成員函數(shù)默認(rèn)是非虛擬函數(shù)。c++通過靜態(tài)綁定來解析一個(gè)非虛函數(shù)調(diào)用。也就是說,如果pb被聲明為B*,并且B有一個(gè)非虛函數(shù),那么pb->f總是調(diào)用B的函數(shù)f。即使在調(diào)用時(shí)pb實(shí)際指向的是一個(gè)D對(duì)象(此中D從B派生而來并且覆寫了函數(shù)),那么調(diào)用pb->f仍舊調(diào)用的是隸屬于B的函數(shù)f,而不是D的f。


從另一方面來說,虛擬成員函數(shù)調(diào)用是動(dòng)態(tài)綁定的。如果pb是B*指針并且f在中被聲明為一個(gè)虛擬成員函數(shù),那么pb->f所實(shí)際調(diào)用的函數(shù)取決于*pb的動(dòng)態(tài)類型。這樣的話,如果pb實(shí)際指向的是B對(duì)象,那么pb->f調(diào)用的隸屬于B的f。而如果pb實(shí)際是想一個(gè)D對(duì)象(D由B派生而來并且覆寫了函數(shù)f),那么pb->f調(diào)用的是D的f。

一個(gè)至少有一個(gè)虛擬成員函數(shù)的類被稱作多態(tài)類型,這樣類型的對(duì)象們展示了多態(tài)性。多態(tài)能讓你為邏輯上相似但實(shí)體行為不同的子類型繼承體系定義統(tǒng)一的接口。通過使用多態(tài),你能夠?qū)⒁粋€(gè)派生類對(duì)象指針或引用傳遞給只知其基類型對(duì)象的函數(shù)。對(duì)象仍將保持其動(dòng)態(tài)類型以使得成員函數(shù)調(diào)用能夠施行派生類的行為。


listing.1
class shape
{
public:
enum palette { BLUE, GREEN, RED };
shape(palette c);
virtual double area() const;
virtual const char *name() const;
virtual ostream &put(ostream &os) const;
palette color() const;
private:
palette_color;
static const char *color_image[RED - BLUE + 1];
};

inline ostream &operator<<(ostream &os, const shape &s)
{
return s.put(os);
}

// End of File

Listing 1顯示了shape類定義,一個(gè)多態(tài)的幾何類。

Listing 2 Member function and static member data definitions for class shape

shape::shape(palette c) : _color(c) { }
shape::palette shape::color( ) const
{
return _color;
}

double shape::area() const
{
return 0;
}

const char *shape::name() const
{
return "point";
}

ostream &shape::put(ostream &os) const
{
return os << color_image[_color] << '' << name();
}

const char *shape::color_image[shape::RED - shape::BLUE + 1] =
{ "blue", "green", "red" };
// End of File
Listing 2顯示了相應(yīng)的成員函數(shù)和靜態(tài)成員函數(shù)定義。Class shape有三個(gè)虛擬函數(shù),area,name 和put,兩個(gè)非虛成員函數(shù),color和shape(一個(gè)ctor)。

Listing 3 Class circle derived form shape
class circle : public shape
{
public:
circle(palette c, double r);
double area() const;
const char *name() const;
ostream &put(ostream &os) const;
private:
double radius;
};

circle::circle(palette c, double r) : shape(c), radius(r) { }
double circle::area() const
{
const double pi = 3.1415926;
return pi * radius * radius;
}

const char *circle::name() const
{
return "circle";
}
ostream &circle::put(ostream &os) const
{
return shape::put(os) << "with radius = " << radius;
}
// End of File
Listing 4 Class rectangle derived from shape
class rectangle : public shape
{
public:
rectangle(palette c, double h, double w);
double area() const;
const char *name() const;
ostream &put(ostream &os) const;

private:
double height, width;
};
rectangle::rectangle(palette c, double h, double w)
: shape(c), height(h), width(w) { }
double rectangle::area() const
{
return height * width;
}

const char *rectangle::name() const
{
return "rectangle";
}

ostream &rectangle::put(ostream &os) const
{
return shape::put(os) << " with height = " << height
<< " and width = " << width;
}
// End of File
Listing 3 和Listing 4相應(yīng)的展示了類circle 和 rectangle完整定義,這兩個(gè)類都從shape派生而來。每一個(gè)派生類定義了自己的構(gòu)造函數(shù),并且使用合適的定義將各自繼承而來的虛函數(shù)覆寫了。
Listing 5 A function that returns the shape with the largest area in a collection of shapes

const shape *largest(const shape *sa[], size_t n)
{
const shape *s = 0;
double m = 0;
double a;

for (size_t i = 0; i < n; ++i)
if ((a = sa[i]->area()) > m)
{
m = a;
s = sa[i];
}

return s;
}

// End of File

Listing 5包含了一個(gè)函數(shù)展示了多態(tài)的威力。函數(shù)largest從一個(gè)shapes集合中找到具有最大面積的shape。既然shape是多態(tài)類型,那么調(diào)用sa[i]->area不用調(diào)用者準(zhǔn)確知曉*sa[i]實(shí)際的shape類型就能返回其面積。

vptrs and vtbls

ARM(Ellis)和新興的c++標(biāo)準(zhǔn)都在竭力描述虛函數(shù)的行為特征,就如同其所描述的C++語言的其他部分,而沒有沒有建議具體的實(shí)現(xiàn)策略。但是,ARM里面在第十章的結(jié)尾派生類的評(píng)注中確實(shí)描述了實(shí)現(xiàn)技術(shù)。我覺得仰仗一個(gè)模型來實(shí)現(xiàn)簡化了虛擬函數(shù)習(xí)性的細(xì)節(jié)描述。下面就是這樣的一個(gè)模型。

典型的c++實(shí)現(xiàn)里為每一個(gè)多態(tài)類的對(duì)象增加了一個(gè)指針。這個(gè)指針被稱作vptr。不論何時(shí)一個(gè)多態(tài)類的構(gòu)造函數(shù)初始化一個(gè)對(duì)象時(shí),它都會(huì)將對(duì)象的vptr設(shè)置為一個(gè)叫做vtbl的函數(shù)指針表的地址。vtbl中的每一個(gè)條目都一個(gè)虛函數(shù)的地址。一個(gè)既定類的所有對(duì)象都共享同樣的vtbl;這個(gè)vtbl包含了包含了該類中的每一個(gè)虛函數(shù)的入口地址。

Figure 1 Layout of class shape


例如,上圖顯示了類shape的一個(gè)對(duì)象的布局(在listing 1中定義的)和其相應(yīng)的vtbl。每一個(gè)shape對(duì)象都擁有同樣順序的兩個(gè)值域:vptr和一個(gè)_colo 成員數(shù)據(jù)。vptr指向了shape的vtbl,包含了shape虛函數(shù)的地址(shape::area,shape::name和shape::put)。非虛函數(shù)shape::color和shape::shape(構(gòu)造函數(shù))不會(huì)在vtbl中占用任何空間,也不會(huì)占用對(duì)象本身的任何空間。

Figure 2 Layout of class circle

Figure 3 Layout of class rectangle


圖2和圖3相應(yīng)顯示了circle和rectangle對(duì)象及其相關(guān)的vtbl的布局。注意到circle和reatangle對(duì)象起始一部分是shape對(duì)象,所以一個(gè)指向circle或者rectangle的指針同時(shí)也是一個(gè)指向shape的指針,同時(shí)從一個(gè)circle *或者ractangle*轉(zhuǎn)換成shape* 不需要指針計(jì)算。

兩個(gè)派生類的vtbls和基類的vtbl擁有同樣的函數(shù)指針序,盡管指針值是不同的。例如,area函數(shù)的vtbl入口在每一個(gè)從shape派生而來的類中都排在第一位。name的vtbl入口總在第二,put的入口序總為3。

然而一個(gè)非虛函數(shù)調(diào)用所產(chǎn)生的調(diào)用指令直接指向了在轉(zhuǎn)換中(編譯和鏈接)就已確定的地址,一個(gè)虛函數(shù)調(diào)用產(chǎn)生的額外的代碼以定位vtbl中的函數(shù)地址。


ARM一書中建議將vtbl看作函數(shù)地址的一個(gè)隊(duì)列,以使得每一次對(duì)被調(diào)用函數(shù)的定位能夠vtbl的下標(biāo)來定位。比如,如果ps只一個(gè)指向shape的指針,那么
a = ps->area();
會(huì)被轉(zhuǎn)換成如下這個(gè)樣子:
a = (*(ps->vtbl[0]))(ps);
同樣
ps->put(cout);
會(huì)被轉(zhuǎn)換成
(*(ps->vtbl[2]))(ps,cout);


形如ps->vtbl[n]的表達(dá)式就表示了*ps對(duì)象vtbl的入口,所以 (*(ps->vtbl [n])) 就是第nth虛擬函數(shù)自身了。實(shí)際上,如同在c語言里一樣,你不需要在調(diào)用表述里顯式提領(lǐng)一個(gè)函數(shù)指針,你可以將
(*(ps->vtbl[2])) (ps, cout);
簡化成
(ps->vtbl[2])(ps, cout);


每一個(gè)虛函數(shù)可能會(huì)有不同的簽名式(形式參數(shù)類型次序)和返回類型返回類型。所以嚴(yán)格來講,你不能將vtbls實(shí)現(xiàn)成函數(shù)數(shù)組,因?yàn)閿?shù)組需要其所有類型都具有同樣的類型。比如, shape::area 類型為 double (*)() shape::put 類型為 void (*)(ostream &) .

我寧愿將vtbl模塑為一個(gè)結(jié)構(gòu),其內(nèi)所有的成員都是指向函數(shù)的指針。具體來講,你可以為shape的vtbl定義如下的結(jié)構(gòu)類型
struct shape_virtual_table
{
double (*area)();
const char *(*name)();
ostream &(*put)(ostream &os);
};


并且定義實(shí)際的shape vtbl如下:

shape_virtual_table shape_vtbl =
{
&shape::area,
&shape::name,
&shape::put
};


與此類似,你能夠如下定義circle vtbl:

shape_virtual_table circle_vtbl =
{
&circle::area,
&circle::name,
&circle::put
};

(我說“類似這樣”,因?yàn)檫@樣的代碼實(shí)際通不過編譯,這樣的代碼只是演示下vtbl的通常的布局)使用這樣的轉(zhuǎn)換模式,
a = ps->area();
轉(zhuǎn)換成
a = (*ps->vtbl->area)(ps);
或簡化版
a = ps->vtbl->area(ps);
同樣
ps->put(cout);
轉(zhuǎn)換成
(*ps->vtbl->put)(ps, cout);
就是
ps->vtbl->put(ps, cout);


一次具有n個(gè)參數(shù)的虛擬函數(shù)調(diào)用將被轉(zhuǎn)換成具有n+1個(gè)參數(shù)的調(diào)用(通過vtbl入口)。增加的那個(gè)參數(shù)就是被應(yīng)用函數(shù)的對(duì)象的地址;在上例中,其值總是ps。在被調(diào)用函數(shù)中,這個(gè)額外的參數(shù)就成了this的值。虛函數(shù)不能為靜態(tài)成員,所以他們總是隱式的有一個(gè)this參數(shù)。

記住我所描述的只是一種典型的實(shí)現(xiàn)策略。c++ translators 可能在實(shí)現(xiàn)虛函數(shù)時(shí)并不一致,但是效果是一樣的。vptr并不需要在每個(gè)多態(tài)對(duì)象的開頭。但是,對(duì)于任何從多態(tài)類型B派生而來的類D而言,D的vptr在D中的偏移量和B的vptr在B中的偏移量一致。類似地,vtbl中的函數(shù)指針的順序也并不一定和類中聲明的順序一致,但對(duì)于任何任何從多態(tài)類B派生而來的D,D的vtbl的初始部分必須和B的vtbl的布局一致,即便因D已經(jīng)覆寫了某些所繼承的虛函數(shù)而造成D的實(shí)際的指針值與B的不同。


簡而言之,一個(gè)C++ TRANSLATOR必須確保派生對(duì)象的基子對(duì)象與任何基類型對(duì)象的布局一致,并且派生類型vtbl基portion部分和基類對(duì)象的vtbl一致。因此,當(dāng)translator轉(zhuǎn)換一個(gè)虛函數(shù)調(diào)用時(shí)不需要預(yù)見任何派生類的聲明。不考慮p的動(dòng)態(tài)類型,一個(gè)如p->f這樣的虛函數(shù)調(diào)用總是轉(zhuǎn)成這樣的代碼
構(gòu)造f的實(shí)際實(shí)際參數(shù)列表
循p的vtpr到一個(gè)vtbl
將控制權(quán)移交給vtbl中相應(yīng)指向f的入口地址。


所有既定多態(tài)類型的多態(tài)對(duì)象能夠共享共同的vtbl實(shí)體。一些c++成功剔除了vtbls的重復(fù)。另一些產(chǎn)生多酚vtbls的拷貝,由于開發(fā)環(huán)境所限或者為提供更好的系統(tǒng)性能。許多實(shí)現(xiàn)提供了編譯和鏈接選項(xiàng)讓你自行作出決定。

這個(gè)實(shí)現(xiàn)模型展示了虛函數(shù)導(dǎo)入了小小的時(shí)間和空間上的代價(jià):
為一個(gè)已有虛函數(shù)的類增加了一個(gè)或多個(gè)虛函數(shù)并沒有為該類的每一個(gè)對(duì)象增加一個(gè)vptr。
每一個(gè)多態(tài)類增加了至少多增加了一個(gè)vtbl到程序數(shù)據(jù)區(qū)。
多態(tài)類的每一個(gè)構(gòu)造函數(shù)必須初始化vptr
每一個(gè)虛函數(shù)調(diào)用必須定位通過查詢vtbl以定位函數(shù)地址(通常需要2-4條額外的機(jī)器指令)
在c++中,成員函數(shù)默認(rèn)為非虛的,因?yàn)閏++堅(jiān)持原則“不為不使用的部分付出代價(jià)”。如果你樂意承擔(dān)虛函數(shù)調(diào)用的代價(jià),你得明確的說出來。

選擇性的覆寫

派生類或多或少或不覆寫基類中的虛函數(shù)。一個(gè)派生類繼承了其所未覆寫的虛函數(shù)的定義。Listing 6 和 圖4共同例示了選擇性地覆蓋基類中的虛函數(shù)的效果。
Listing 6 Selective virtual overriding
#include <iostream.h>
class B
{
public:
virtual void f();
virtual void g();
virtual void h();
};
class C : public B
{
public:
void f(); // virtual
};

class D : public C
{
public:
void h(); // virtual
};

void B::f()
{
cout << "B::f()/n";
}

void B::g()
{
cout << "B::g()/n";
}

void B::h()
{
cout << "B::h()/n";
}

void C::f()
{
cout << "C::f()/n";
}

void D::h()
{
cout << "D::h()/n";
}

int main()
{
C c;
D d;
B *pb = &c; // ok, &c is a C * which is a B *
pb->f(); // calls C::f()
pb->g(); // calls B::g()
pb->h(); // calls B::h()
C *pc = &d; // ok, &d is a D * which is a C *
pc->f(); // calls C::f()
pc->g(); // calls B::g()
pc->h(); // calls D::h()
B &rb = *pc; // ok, *pc is a C which is a B
rb.f(); // calls C::f()
rb.g(); // calls B::g()
rb.h(); // calls D::h()
return 0;
}

// End of File

Figure 4 Selectively overriding only some virtual functions



listing 6 顯示了一個(gè)簡單的class 繼承體系,圖4顯示了相應(yīng)的vtbls。類B定義了三個(gè)虛函數(shù)f,g和h。由B派生而來的C只覆寫了函數(shù)f,所以C的vtbl中的g和h的入口仍舊是B的g和h。由C派生的類D只覆寫了函數(shù)h,所以D的vtbl中的f和g的入口和C的vtbl中的一致。既然C和D都沒有覆寫g,所有三個(gè);類的vtbl對(duì)于g的入口都具有同樣的值,也就是B'g。

在Listing 6中,pc有一個(gè)靜態(tài)類型C*.但是當(dāng)程序執(zhí)行到這句前
B &rb = *pc;
pc的動(dòng)態(tài)類型為D*。所以所有應(yīng)用到rb上的調(diào)用都使用D的vtbl。

c++ 虛函數(shù) 工作機(jī)制 原理( virtual function )


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號(hào)聯(lián)系: 360901061

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對(duì)您有幫助就好】

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會(huì)非常 感謝您的哦!!!

發(fā)表我的評(píng)論
最新評(píng)論 總共0條評(píng)論
主站蜘蛛池模板: 免费看真人a一级毛片 | 成人18毛片 | 久久99爱视频 | 国产精品久久久久久久久kt | 亚洲乱人伦在线 | 午夜一级毛片 | 老子影院午夜精品欧美视频 | 国产一级aaa全黄毛片 | 综合99| 精品日韩在线观看 | 青青青青啪视频在线观看 | 欧洲成人爽视频在线观看 | 久青草视频免费视频播放线路1 | 日韩免费观看一级毛片看看 | 热re99久久精品国产99热 | 4hu影院永久在线播放 | 亚州视频在线 | 欧美精品啪啪 | 伊人色综合久久天天爱 | 老妇激情毛片免费中国 | 久久亚洲精品中文字幕亚瑟 | 性欧美疯狂猛交69hd | 女人夜色黄网在线观看 | 91福利国产在线观看一区二区 | 日本成a人免费视频 | 有码中文字幕在线观看 | 久久天天躁夜夜躁狠狠85麻豆 | 国产日 | 一本久道久久综合狠狠爱 | 中文字幕在线视频观看 | 日韩欧美中文字幕一区 | 久草经典视频 | 久久人人精品 | 青青草论坛 | 国产三级久久 | 播五月综合 | 国产亚洲人成a在线v网站 | 免费久久精品 | 夜夜欢视频 | 国产成人精品一区二区视频 | 亚洲四虎影院 |