單繼承虛函數(shù)例子

class Point
{
public:
virtual ~Point();

virtual Point& mult( float ) = 0;
//
other operations 


float x() const
{ return _x; }

virtual float y() const
{ return 0; }

virtual float z() const
{ return 0; }
// 

protected:
Point( float x = 0.0 );
float _x;
};

class Point2d : public Point
{
public:
Point2d( float x = 0.0, float y = 0.0 )

: Point( x ), _y( y )
{}
~Point2d();

// overridden base class virtual functions
Point2d& mult( float );

float y() const
{ return _y; }

//
other operations 

protected:
float _y;
};

class Point3d: public Point2d
{
public:
Point3d( float x = 0.0,
float y = 0.0, float z = 0.0 )

: Point2d( x, y ), _z( z )
{}
~Point3d();

// overridden base class virtual functions
Point3d& mult( float );

float z() const
{ return _z; }

//
other operations 
protected:
float _z;
};
-
Vtable和VPTR結(jié)構(gòu)
虛函數(shù)的實現(xiàn)是通過VTable和vptr。每一個帶有虛函數(shù)的類都有一個VTable,在編譯器生成,每一個帶有虛函數(shù)的類實例都有一個vptr,該類實例vptr指向該類的VTable,在運行期生成。
如圖左部的類實例內(nèi)存結(jié)構(gòu),編譯器為之生成__vptr__Point的指針,指向該類的VTable。

VTable的結(jié)構(gòu)是一個函數(shù)指針數(shù)組,數(shù)組的每個元素是一個函數(shù)指針,指向該類虛函數(shù)的地址。因為基類Point的Point::mult()為純虛函數(shù),因此Point對應(yīng)的mult函數(shù)指針指向一個pure_virtual_called(),拋出調(diào)用純虛函數(shù)錯誤。
如圖VTable所示,Point類和其子類的析構(gòu)函數(shù)均在VTable[1],mult在VTable[2],y在VTable[3],z在VTable[4]。如果Point2d增加Point2d自己的虛函數(shù),同時Point3d繼承Point2d的虛函數(shù),他們相同的虛函數(shù)接口同樣對應(yīng)于相同的VTable數(shù)組下標(biāo),如VTable[5],此由編譯器保證,因而編譯器對于虛函數(shù)接口能將其轉(zhuǎn)換為函數(shù)指針數(shù)組的下標(biāo)。
故,當(dāng)調(diào)用
ptr->z();
編譯器實際調(diào)用的是:
( *ptr->vptr[ 4 ] )( ptr );
從而可以找到ptr實際指向的VTable中的虛函數(shù)調(diào)用地址。
-
虛函數(shù)系統(tǒng)開銷
為了實現(xiàn)虛函數(shù),編譯器產(chǎn)生的操作包括:
-
編譯期,為每一個類增加一個VTable函數(shù)指針數(shù)組,并使其指向正確的虛函數(shù)實現(xiàn)。
-
運行期,在類的構(gòu)造函數(shù)中,為每一個類實例增加一個vptr,指向該類的VTable。
-
編譯器,將虛函數(shù)調(diào)用編譯為函數(shù)指針的調(diào)用。
-
運行期,在虛函數(shù)調(diào)用時,通過指向VTable和調(diào)用函數(shù)的index,查找函數(shù)指針(查找效率為數(shù)組隨機訪問,常數(shù)時間),調(diào)用虛函數(shù)。
由分析得,虛函數(shù)開銷主要在編譯期的VTable函數(shù)指針數(shù)組的構(gòu)造,而運行期的函數(shù)指針查找不是性能瓶頸。同時,一個帶虛函數(shù)的基類無論有多少個孩子類,并不會降低虛函數(shù)性能,而如果類的繼承層次太深,底層類實例的構(gòu)造函數(shù)則需要為類繼承層次的每一層父類初始化vptr,效率降低。
-
虛函數(shù)系統(tǒng)性能測試
void
cross_product( const pt3d &pA, const pt3d &pB )


{
pt3d pC;

pC.x = pA.y * pB.z - pA.z * pB.y;
pC.y = pA.z * pB.x - pA.x * pB.z;
pC.z = pA.x * pB.y - pA.y * pB.x;
}

main()
{
pt3d pA( 1.725, 0.875, 0.478 );
pt3d pB( 0.315, 0.317, 0.838 );

for ( int iters = 0; iters < 10000000; iters++ )
cross_product( pA, pB );

return 0;
}
Optimized Non-optimized
Inline Member 0.08 4.70
Nonstatic Member 4.43 6.13
Virtual Member
CC 4.76 6.90
NCC 4.63 7.72
CC和NCC是比較的兩個編譯器版本,對于上述計算函數(shù)的測試,虛函數(shù)的調(diào)用開銷主要是3.4虛表查詢,虛函數(shù)調(diào)用損失了4% 到11%的運行時間。相對于IO操作,可以忽略。