C++虛指針實現(xiàn)及效率
-
單繼承虛函數(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對應的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ù)接口同樣對應于相同的VTable數(shù)組下標,如VTable[5],此由編譯器保證,因而編譯器對于虛函數(shù)接口能將其轉(zhuǎn)換為函數(shù)指針數(shù)組的下標。
故,當調(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操作,可以忽略。
-
-
參考文獻
Inside the C++ Object Model, By Stanley B. Lippman
posted on 2011-10-24 16:00 楊粼波 閱讀(1363) 評論(0) 編輯 收藏 引用 所屬分類: C++