C++對象模型學習——Function語意學

       若是有一個Point3d的指針和對象:ios

Point3d obj;
Point3d *ptr = &obj;

       當這樣作:程序員

obj.normalize();
ptr->normalize();

        時,會發生什麼事?其中的Point3d::normalize()定義以下:express

Point3d Point3d::normalize() const
{
  register float mag = magnitude();
  Point3d normal;

  normal._x = _x / mag;
  normal._y = _y / mag;
  normal._z = _z / mag;

  return normal;
}

        而其中的Point3d::magnitude()又定義以下:安全

float Point3d::magnitude() const
{
  return sqrt( _x * _x + _y * _y + _z * _z );
}

       答案是不明確的。C++支持三種類型的member functions:static、nonstatic和virtual,每一ide

種類型被調用的方式都不一樣。不過咱們雖不能肯定normalize()和magnitude()兩函數是否爲函數

virtual或nonvirtual,但能夠肯定它必定不是static,緣由有二:(1)它直接存取nonstatic數據,(2)工具

它被聲明爲const。而static member functions不可能作到這兩點。佈局

1、Member的各類調用方式

       一、非靜態成員函數(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 magnitude3d( const Point3d *_this )
{
  return sqrt( _this->_x * _this->_x +
               _this->_y * _this->_y +
               _this->_z * _this->_z );
}

        咋看之下彷佛nonmember function比較沒有效率,它間接地經由參數取用座標成員,而

member function倒是直接取用座標成員。然而實際上member function被內化爲nonmember的

形式。下面是轉化步驟:

        1)改寫函數的signature(函數原型)以安插一個額外的參數到member function中,用以

提供一個存取管道,使class object得以將此函數調用。該額外參數被稱爲this指針:

// non-const nonstatic member的擴張過程
Point3d Point3d::magnitude( Point3d *const this )

             若是member function 是const,則變爲:

// const nonstatic member的擴張過程
Point3d Point3d::magnitude( const Point3d *const this )

        2)將每個」對nonstatic data member的存取操做「改成經由this指針來存取:

{
  return sqrt( this->_x * this->_x +
               this->_y * this->_y +
               this->_z * this->_z );
}

        3)將member function從新寫成一個外部函數。將函數名稱通過」mangling「處理,使它在程

序中成爲獨一無二的語彙:

extern magnitude_7Point3dFv(
   register Point3d *const this );

        如今這個函數已經被轉換好了,而其每個調用操做也都必須轉換。因而:

obj.magnitude();

// 轉換爲:
magnitude_7Point3dFv( &obj );

         而

ptr->magnitude();

// 轉換爲:
magnitude_7Point3dFv( ptr );

        而normalize()函數會被轉化爲下面的形式,其中假設已經聲明有一個Point3d copy

constructor,而named returned value(NRV)的優化也已經實施:

// 如下描述」named return value函數「的內部轉化
// 使用C++代碼
void normalize_7Point3dFv( register const Point3d *const this, 
                           Point3d &_result )
{
  register float mag = this->magnitude();
  
  // default constructor
  _result.Point3d::Point3d();

  _result._x = this->_x / mag;
  _result._y = this->_y / mag;
  _result._z = this->_z / mag;

  return;
}

           一個有效率的作法是直接構建」normal「值,像這樣:

Point3d Point3d::normalize() const
{
  register float mag = magnitude();
  return Point3d( _x / mag, _y / mag, _z / mag ); 
}

            它會轉化爲如下的代碼(再一次假設Point3d的copy constructor已經聲明好了,而NRV

的優化也已實施):

// 如下描述內部轉化
// 使用C++僞碼
void normalize_7Point3dFv( register const Point3d *const this, Point3d & _result )
{
  register float mag = this->magnitude();

  // _result用以取代返回值(return value)
  _result.Point3d::Point3d( this->_x / mag, this->_y / mag, this->_z / mag );

  return;
}

          這能夠節省default constructor初始化所引發的額外負擔。

       二、名稱的特殊處理(Name Mangling)

       通常而言,member的名稱前面會被加上class名稱,造成獨一無二的命令。例以下面的聲

明:    

class Bar { public: int ival; ... };

       其中的ival有可能變成這樣:

// member通過name-mangling以後的可能結果之一
ival_3Bar

       爲何編譯器要這麼作?清考慮這樣的派生操做(derivation):

class Foo : public Bar { public: int val;... };

       Foo對象內部結合了base class和derived class二者:

// C++僞碼
// Foo的內部描述
class Foo
{
  public:
    int ival_3Bar;
    int ival_3Foo;
};

        無論處理哪一個ival,經過」name mangling「,均可以絕對清楚地指出來。因爲member

functions能夠被重載化(overload),因此須要更普遍的mangling手法,以提供對獨一無二的

名稱。若是把:

class Point
{
  public:
    void x( float newX );
    float x();
    ...
};

         轉換爲:

class Point
{
  public:
    void x_5Point( float newX );
    float x_5Point();
  ...
};

       會致使兩個被重載化(overloaded)的函數實例擁有相同的名稱。爲了讓它們獨一無二,惟

有再加上它們的參數鏈表(能夠從函數原型中參考獲得)。若是把參數類型也編碼進取,就一

定能夠製造出獨一無二的結果,使咱們的兩個x() 函數有良好的轉換(若是聲明extern 」C「,就會

壓抑nonmember functions的」mangling「效果):

class Point
{
  public:
    void x_5PointFf( float newX );
    float x_5PointFv();
    ...
};

        把參數和函數名稱編碼在一塊兒,編譯器因而在不一樣的編譯模塊之間達成了一種優先形式的

類型檢驗。以下print函數被這樣定義:

void print( const Point3d& ) { ... }

         但意外地被這樣聲明和調用:

// 覺得是const Point3d&
void print( const Point3d );

         兩個實例若是擁有獨一無二的name mangling,那麼任何不正確的調用操做在連接時期就

因沒法決議(resolved)而失敗。但若是是」返回類型「聲明錯誤就沒辦法檢查出來。

       三、虛擬成員函數(Virtual Member Functions)

       若是normalize()是一個virtual member function,那麼如下的調用:

ptr->normalize();

        將會被內部轉化爲:

( *ptr->vptr[ 1 ] )( ptr );

        其中:

        1)vptr表示由編譯器產生的指針,指向virtual table。它被安插在每個「聲明有(或繼承

自),一個或多個virtual functions」的class object中。其名稱也會被「mangled」,由於在一個復

雜的class派生體系中,可能存在多個vptrs。

         2)1是virtual table slot的索引值,關聯到normalize()函數。

         3)第二個ptr表示this指針。

          一樣,若是magnitude()也是一個virtual function,它在normalize()之中的調用操做將被轉

換以下:

// register float mag = magnitude();
register float mag = ( *this->vptr[ 2 ] )( this );

          因爲Point3d::magnitude()是在Point3d::normalize()中被調用的,然後者已經由虛擬機制而

決議穩當,因此顯示地調用「Point3d實例」會比較有效率,並所以壓制因爲虛擬機制而產生的不

必要重複調用操做:

// 顯示的調用操做(explicitly invocation)會壓制虛擬機制
register float mag = Point3d::magnitude();

            若是magnitude()聲明爲inline函數,會更有效率。使用class scope operator顯示調用一

個virtual function,其決議方式會和nonstatic member function同樣:

register float mag = magnitude_7Point3dFv( this );

            對於如下調用:

// Point3d obj;
obj.normalize();

            若是編譯器把它轉換爲:

( *obj.vptr[ 1 ] )( &obj );

            雖然語意正確,卻沒有必要。」經由一個class object 調用一個virtual function「,這種操做

應該老是被編譯器像對待通常nonstatic member function同樣地加以決議:

normalize_7Point3dFv( &obj );

             這項優化的一利益是,virtual function的一個inline函數實例能夠被擴展(expanded)開

來,於是提供極大的效率利益。 

       四、靜態成員函數(Static Member Functions)

           若是Point3d::normalize()是一個static member function,如下兩個調用操做:

obj.normalize();
ptr->normalize();

           將被轉換爲通常的nonmember函數調用,以下:

// obj.normalize();
normalize_7Point3dSFv();
// ptr->normalize();
normalize_7Point3dSFv();

            在C++引入static member functions以前,不多會看到以下怪異寫法:

( ( Point3d* )0 )->object_count();

            其中的object_count只是簡單傳回_object_count這個static data member。

            在引入static member functions以前,C++語言要求全部的member functions都必須經由

該class的object來調用。而實際上,只有當一個或多個nonstatic data members在member

function中被直接存取時,才須要class object。Class object提供了this指針給這種形式的函數

調用使用。這個this指針把」在member function中存取的nonstatic class members「綁定於」object

內對應的members「之上。若是沒有任何一個members被直接存取,事實上就不須要this指針,

所以也就不必經過一個class object來調用一個member function。

            這麼一來就存取static data members時產生了一些不規則性。若是class的設計者把static

data member聲明爲nonpublic(這一直被視爲一種好的習慣),那麼他就必須提供一個或多個

member functions來存取該member。所以,雖然你能夠不靠class object來存取一個static

member,但其存取函數卻得綁定於一個class object之上。

            獨立於class object以外的存取操做,在某個時候特別重要:當class設計者但願支持」沒

有class object存在「的狀況時。程序方法上的解決之道是很奇特意把0強制轉換爲一個class指

針,於是提供出一個this指針實例:

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

            Static member functions的主要特性就是它沒有this指針。如下次要特性通通根源於其主

要特性:

            1)它不能直接存取其class中的nonstatic members。

            2)它不可以被聲明爲const、volatile或virtual。

            3)它不須要經由class object才被調用——雖然大部分時候它是這樣被調用的!

            「member selection」語法的使用是一種符號上的便利,它會被轉化爲一個直接調用操做:

if( Point3d::object_count() > 1 ) ...

             若是class object是由於某個表達式而得到的,會如何?例如:

if( foo().object_count() > 1 ) ...

             這個表達式仍然須要被評估求值:

// 轉化,以保存反作用
( void ) foo();
if( Point3d::object_count() > 1 ) ...

             一個static member function,固然會被提出於class聲明以外,並給予一個通過

「mangled」的適當名字。例如:

unsigned int Point3d::object_count()
{
  return _object_count;
}

            會被cfront轉化爲:

// 在cfront之下的內部轉化結果
unsigned int object_count_5Point3dSFv()
{
  return _object_count_5Point3d;
}

            其中SFv表示它是一個static member function,擁有一個空白(void)的參數鏈表

(argument list)。

             因爲static member function沒有this指針,因此其地址的類型並非一個「指向class

member function的指針」,而是一個「nonmember函數指針」。也就是說:

&Point3d::object_count();

             會獲得一個數值,類型是:

unsigned int (*)();

             而不是:

unsigned int ( Point3d::* )(  );

             Static member function因爲缺少this指針,所以差很少等同於nonmember function。它

提供了一個意想不到的好處:成爲一個callback函數,使咱們得以將C++和C-base X Window系

統結合。它們也能夠成功地應用在線程(threads)函數身上。

2、Virtual Member Function(虛擬成員函數)

         virtual function的通常實現模型:,每個class 有一個virtual table,內含該

class之中有做用的virtual function的地址,而後每一個object有一個vptr,指向virtual

table的所在。

           爲了支持virtual function機制,必須首先可以對於多態對象有某種形式的「執行期類型判斷

(runtime type resolution)」。也就是說如下的調用操做將須要ptr在執行期的某些相關信息:

ptr->z();

           如此一來纔可以找到並調用z()的適當實例。

           或許直截了當可是成本最高的解決方法就是把必要信息加載ptr身上。在這樣的策略之

下,一個指針(或是一個reference)持有兩項信息:

          1)它所參考到的對象的地址(也就是目前它所持有的東西);

          2)對象類型的某種編碼,或是某個結構(內含某些信息,用以正確決議出z()函數實例)

的地址。

           這個方法帶來兩個問題:第一,它明顯增長了空間負擔,即便程序並不使用多態

(polymorphism);第二,它打斷了與C程序間的連接兼容性。

           若是這份額外信息不可以和指針放在一塊兒,下一個能夠考慮的地方就是把它放在對象本

身。可是哪個對象真正須要這些信息呢?咱們應該把這些信息放進可能被繼承的每個集合

體身上呢?也許。但請考慮一下這樣的C struct聲明:

struct date { int m, d, y; };

            這符合上述規則。然而事實上它並不須要那些信息。加上那些信息將使C struct膨脹而且

打破連接兼容性,卻沒有帶來任何明顯的補償利益。

             而面對那些顯示使用了class關鍵詞的聲明,才應該加上額外的執行期信息。這樣作能夠

保持語言的兼容性,不過仍然不是一個夠聰明的政策。例如,下面這個class符合新規則:

class data { public: int m, d, y; };

            但實際上它並不須要那份信息。下面的class聲明雖然不符合新規範,卻須要那份信息:

struct geom { public: virtual ~geom(); ... };

            咱們須要一個以class的使用爲基礎,而不在意關鍵詞是class或struct的規範。若是class

真正須要那份信息,它就會存在;若是不須要,它就不存在。很明顯在必須支持某種形式之「執行

期多態(runtime polymorphism)」的時候須要這份信息。

             在C++中,多態(ploymorphism)表示「一個public base class的指針(或reference),

尋找出一個derived class object」的意思。例以下面的聲明:

Point *ptr;

             咱們能夠指定ptr以尋址出一個Point2d對象:

ptr = new Point2d;

             或是一個Point3d對象:

ptr = new Point3d;

              ptr的多態技能主要扮演一個輸送機制(transport mechanism)的角色,經由它,咱們

能夠在程序的任何地方採用一組public derived類型。這種多態形式被稱爲是消極的(passive)

,能夠在編譯時期完成——virtual base class的狀況除外。        

              當被指出的對象真正被使用時,多態也就變成積極的(active)了。下面對於virtual

function的調用,就是一例:

// "積極多態(active ploymorphism)"的常見例子
ptr->z();

              在funtime type identification(RTTI)性質於1993年被引入C++語言以前,C++對「積極

多態(active polymorphism)」的惟一支持,就是對於virtual function call 的決議操做。有了

RTTI,就可以在執行期查詢一個多態的pointer或多態的reference了:

// "積極多態(active polymorphism)"的第二個例子
if( Point3d *p3d = dynamic_cast<Point3d*>( ptr ) )
  return p3d->_Z;

              因此欲鑑定哪些classes展示多態特性,咱們須要額外的執行期信息。關鍵詞class和

struct並不能幫助咱們。因爲沒有導入像是polymorphic之類的新關鍵詞,所以識別一個class是

否支持多態,惟一適當的方法就是看看它是否有任何virtual function。只要class擁有一個virtual

function,它就須要這份額外的執行期信息。 

            下一個明顯的問題是,什麼樣的額外信息是咱們須要存儲起來的?也就是說,若是有這

樣的調用:

ptr->z();

            其中z()是一個virtual function,那麼什麼信息才能讓咱們在執行期調用正確的z()實例?

須要知道:

            1)ptr所指對象的真實類型。這可以使咱們選擇正確的z()實例。

            2)z()實例的位置,以便可以調用它。

            在實際上,首先能夠在每個多態的class object身上增長兩個members:

            1)一個字符串或數字,表示class的類型。

            2)一個指針,指向某表格,表格中持有程序的virtual functions的執行期地址。

            關於表格中的virtual functions地址如何被構建起來。在C++中,virtual functions(可經由

其class object被調用)能夠在編譯時期獲知。此外,這一組地址是固定不變的,執行期不可能

新增或替換之。因爲程序執行時,表格大小和內容不會改變,因此其建構和存取皆能夠由編譯

器徹底掌控,不須要執行期的任何介入。         

             然而,執行期備妥那些函數地址,只是解答的一半而已。另外一半解答是找到那些地址。

兩個步驟能夠完成這項任務:

            1)爲了找到表格,每個class object被安插了一個由編譯器內部產生的指針,指向該

表格。

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

           這些工做都是由編譯器完成。執行期要作的,只是在特定的virtual table slot中激活virtual

function。

            一個class只會有一個virtual table。每個table內含其對應之class object中全部active

virtual function函數實例的地址。包括:

            1)這一class所定義的函數實例。它會改寫(overriding)一個可能存在的base class

virtual function函數實例。

            2)繼承自base class的函數實例。這是在derived class決定不改寫virtual function時纔會

出現的狀況。

            3)一個pure_virtual_called()函數實例,它既能夠扮演pure virtual function的空間保衛者

角色,也能夠當作執行期異常處理函數(有時候會用到)。

            每個virtual function都被指派一個固定的索引值,這個索引在整個繼承體系中保持與特

定的virtual function的關係。例如咱們的Point class體系中:

#include <iostream>

class Point
{
  public:
    Point( float x = 0.0 ) : _x( x ) { }
    virtual ~Point() { }
    
    virtual int mult( float ) = 0;
    // ...其餘操做
    
    float x() const { return _x; }
    virtual float y() const { return 0; }
    virtual float z() const { return 0; }
    // ...

  protected:
    float _x;
};

class Point2d : public Point
{
  public:
    Point2d( float x = 0.0, float y = 0.0 )
            : Point( x ), _y( y ) { }
    ~Point2d() { }
  
  // 改寫base class virtual functions
  int mult( float y ) { return 1; }
  float y() const { return _y; }
  // ...其餘操做

  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() { }
  
  // 改寫base class virtual functions
  int mult( float z ) { return 2; }
  float z() const { return _z; }
  // ...其餘操做

  protected:
    float _z;
};

int main()
{
  Point2d point2d;
  Point3d point3d;

  std::cout << "sizeof( Point ) = " << sizeof( Point ) << std::endl;
  std::cout << "sizeof( point2d ) = " << sizeof( point2d ) << std::endl;
  std::cout << "sizeof( point3d ) = " << sizeof( point3d ) << std::endl; 

  return 0;
}

         下面是class Point的虛表:

.section	.rodata._ZTV5Point,"aG",@progbits,_ZTV5Point,comdat
	.align 8
	.type	_ZTV5Point, @object
	.size	_ZTV5Point, 28
_ZTV5Point:                         # vtable for Point
	.long	0
	.long	_ZTI5Point          # typeinfo for Point
	.long	_ZN5PointD1Ev       # Point::~Point()
	.long	_ZN5PointD0Ev       # Point::~Point()
	.long	__cxa_pure_virtual
	.long	_ZNK5Point1yEv      # Point::y() const
	.long	_ZNK5Point1zEv      # Point::z() const

        virtual destructor被指派slot 2,3,而mult()被指派slot 4。此例並無mult()的函數定義(因

爲它是一個pure virtual function),因此pure_virtual_called()的函數地址會被放在slot 4。若是

該函數意外地被調用,一般操做是結束這個程序。y()被指派slot 5,z被指派slot 6。x()不是

virtual function因此不存在虛表中。

        下面是class Point2d的虛表:

.section	.rodata._ZTV7Point2d,"aG",@progbits,_ZTV7Point2d,comdat
	.align 8
	.type	_ZTV7Point2d, @object
	.size	_ZTV7Point2d, 28
_ZTV7Point2d:                      # vtable for Point2d
	.long	0
	.long	_ZTI7Point2d       # typeinfo for Point2d
	.long	_ZN7Point2dD1Ev    # Point2d::~Point2d()
	.long	_ZN7Point2dD0Ev    # Point2d::~Point2d()
	.long	_ZN7Point2d4multEf # Point2d::mult(float)
	.long	_ZNK7Point2d1yEv   # Point2d::y() const
	.long	_ZNK5Point1zEv     # Point::z() const

          當一個class派生自Point時,會發生什麼事?

          一共有三種可能性:

         1)它能夠繼承base class所聲明的virtual function的函數實例。正確地說是,該函數實例

的地址會被拷貝到derived class的virtual table的相對應slot之中。 

          2)它可使用本身的函數實例。這表示它本身的函數實例地址必須放在對應的slot之中。

          3)它能夠加入一個新的virtual function。這時候virtual table的尺寸會增大一個slot,而新

的函數實例地址會被放進該slot之中。

           Point2d的virtual table在slot 2,3中指出destructor,而slot 4中指出mult()(取代pure

virtual function)。它本身的y()函數實例地址放在slot 5中,繼承自Point的z()函數實例地址則放

在slot 6中。

          下面是class Point3d的虛表:

.section	.rodata._ZTV7Point3d,"aG",@progbits,_ZTV7Point3d,comdat
	.align 8
	.type	_ZTV7Point3d, @object
	.size	_ZTV7Point3d, 28
_ZTV7Point3d:                      # vtable for Point3d
	.long	0
	.long	_ZTI7Point3d       # typeinfo for Point3d
	.long	_ZN7Point3dD1Ev    # Point3d::~Point3d()
	.long	_ZN7Point3dD0Ev    # Point3d::~Point3d()
	.long	_ZN7Point3d4multEf # Point2d::mult(float)
	.long	_ZNK7Point2d1yEv   # Point2d::y() const
	.long	_ZNK7Point3d1zEv   # Point3d::z() const

           一樣對於派生自Point2d的Point3d,其virtual table中的slot 2,3放置Point3d的

destructor,slot 4放置Point3d::mult()函數地址,slot 5放置繼承自Point2d的y()函數地址,slot 6

放置本身的z()函數地址。

           如今,若是有這樣的式子:

ptr->z();

            如何有足夠的知識在編譯時期設定virtual function的調用呢?

          1)通常而言,在每次調用z()時,並不知道ptr所指對象的真正類型。然而知道經由ptr能夠

存取到該對象的virtual table。

          2)雖然不知道哪個z()函數實例會被調用,但知道每個z()函數地址都被放在slot 6中。

          這些信息使得編譯器能夠將該調用轉化爲:

( *ptr->vptr[ 6 ] )( ptr );

          這一轉化中,vptr表示編譯器所安插的指針,指向virtual table;6表示z()被指派的slot編號

(關係到Point體系的virtual table)。惟一一個在執行期纔可以知道的東西是:slot 6所指的到

底是哪個z()函數實例。

          在一個單一繼承體系中,virtual function 機制的行爲十分良好,不但有效率並且很容易塑

造出模型來。可是在多重繼承和虛擬繼承之中,對virtual functions的支持就沒那麼美好了。

          一、多重繼承下的virtual Functions

          在多重繼承中支持virtual functions,其複雜度圍繞在第二個及後繼的base classes身上,

以及」必須在執行期調整this指針「這一點。如下面的class體系爲例:

#include <iostream>

class Base1
{
  public:
    Base1( float base1 ) : data_Base1( base1 ) {  }
    virtual ~Base1() { }
    virtual void speakClearly() { data_Base1 += 1000.0; }
    virtual Base1 *clone() const { return new Base1( this->data_Base1 ); }

  protected:
    float data_Base1;
};

class Base2
{
  public:
    Base2( float base2 ) : data_Base2( base2 ) {  }
    virtual ~Base2() { }
    virtual void mumble() { data_Base2 -= 1.0; }
    virtual Base2 *clone() const { return new Base2( this->data_Base2 ); }

  protected:
    float data_Base2;
};

class Derived : public Base1, public Base2
{
  public:
    Derived( float data1, float data2,  float derived )
             :  Base1( data1 ), Base2( data2 ), data_Derived( derived ) { }
    virtual ~Derived() { }
    virtual Derived *clone() const { return new Derived( this->data_Base1, this->data_Base2, this->data_Derived ); }

  protected:
    float data_Derived;
}; 

int main()
{
  Base1 base1( 1.0 );
  Base2 base2( 2.0 );
  Derived derived( 1.0, 2.0, 3.0 );

  std::cout << "sizeof( base1 ) = " << sizeof( base1 ) << std::endl;
  std::cout << "sizeof( base2 ) = " << sizeof( base2 ) << std::endl;
  std::cout << "sizeof( derived ) = " << sizeof( derived ) << std::endl;

  return 0;
}

          "Derived支持virtual functions"的困難度,通通落在Base2 subobject身上。有三個問題需

要解決,以此而言分別是(1)virtual destructor,(2)被繼承下來的Base2::mumble(), (3)一組

clone()函數實例。

           首先,把一個從heap中配置而得的Derived對象的地址,指定給一個Base2指針:

Base2 *pbase2 = new Drived;

            新的derived對象的地址必須調整以指向其Base2 subobject。編譯時期會產生如下的代

碼:

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

             若是沒有這樣的調整,指針的任何」非多態運用「都將失敗:

// 即便pbase2被指定一個Derived對象,這也應該沒有問題
pbase2->data_Base2;

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

// 必須首先調用正確的virtual destructor函數實例
// 而後施行delete運算符
// pbase2 可能須要調整,以指出完整對象的起始點
delete pbase2;

              指針必須被再一次調整,以求再一次指向Drived對象的起始處。然而上述的offset加法

卻不可以在編譯時期直接設定,由於pbase2所指的真正對象只有在執行期才能肯定。

             通常規則是,經由指向」第二或後繼之base class「的指針(或reference)來調用derived

class virtual function。

Base2 *pbase2 = new Derived;
...
delete pbase2; // invoke derived class's destructor( virtual )

             其所連帶的必要的」this指針調整」操做,必須在執行期完成。也就是說,offset的大小,

以及把offset加到this指針上頭的那一小段程序代碼,必須由編譯器在某個地方插入。問題是,

在哪插入?

            Bjarne原先實施於cfront編譯器中的方法是將virtual table 加大,使它容納此處所須要的

this指針,調整相關事物。每個virtual table slot,再也不只是一個指針,而是一個集合體,內含

可能的offset以及地址。因而virtual function的調用操做由:

( *pbase2->vptr[ 1 ] )( pbase2 );

            改變爲:

( *pbase2->vptr[ 1 ].faddr )
  ( pbase2 + pbase2->vptr[ 1 ].offset );

            其中faddr內含virtual function地址,offset內含this指針調整值。

            這個作法的缺點是,它至關於連坐「處罰」了全部的virtual function調用操做,無論它們是

否須要offset的調整。

             比較有效率的解決方法是利用所謂的thunk。所謂thunk是一小段assembly代碼,用來(1)

以適當的offset值調整this指針,(2)跳到virtual function去。例如,經由一個Base2指針調用

Derived destructor,其相關的thunk可能看起來是這個樣子:

// 虛擬C++代碼
pbase2_dtor_thunk:
  this += sizeof( base1 );
  Derived::~Derived( this );

            Thunk技術容許virtual table slot繼續內含一個簡單的指針,所以多重繼承不要任何空間上

的額外負擔。Slots中的地址能夠直接指向virtual function,也能夠指向一個相關的thunk(如需

調整this指針的話)。

             調整this指針的第二個額外負擔就是,因爲兩種不一樣的可能:(1)經由derived class(或

第一個base class)調用,(2)經由第二個(或後繼)base class調用,同一函數在virtual table

中可能須要多筆對應的slots。例如:

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

delete pbase1;
delete pbase2;

            雖然兩個delete操做致使相同的Derived destructor,可是它們須要兩個不一樣的virtual

table slots:

           1)pbase1不須要調整this指針(由於Base1是最左端base class之故,它已經指向

Derived對象的起始處)。其virtual table slot需放置真正的destructor地址。

            2)pbase2須要調整this指針。其virtual table slot須要相關的thunk地址。

            在多重繼承之下,一個derived class內含n - 1個額外的virtual tables,n表示其上一層

base classes的個數(所以,單一繼承不會有額外的virtual tables)。對於本例的Derived而

言,會有兩個virtual tables被編譯器產生出來:

             1)一個主要實例,與Base1(最左端base class)共享。

             2)一個次要實例,與Base2(第二個base class)有關。

             針對每個virtual tables,Derived對象中有對應的vptr。

            class Base1的虛表:

.weak	_ZTV5Base1
	.section	.rodata._ZTV5Base1,"aG",@progbits,_ZTV5Base1,comdat
	.align 8
	.type	_ZTV5Base1, @object
	.size	_ZTV5Base1, 24
_ZTV5Base1:                               # vtable for Base1
	.long	0  
	.long	_ZTI5Base1                # typeinfo for Base1
	.long	_ZN5Base1D1Ev             # Base1::~Base1()
	.long	_ZN5Base1D0Ev             # Base1::~Base1()
	.long	_ZN5Base112speakClearlyEv # Base1::speakClearly()
	.long	_ZNK5Base15cloneEv        # Base1::clone() const

             class Base2的虛表:

.weak	_ZTV5Base2
	.section	.rodata._ZTV5Base2,"aG",@progbits,_ZTV5Base2,comdat
	.align 8
	.type	_ZTV5Base2, @object
	.size	_ZTV5Base2, 24
_ZTV5Base2:                         # vtable for Base2
	.long	0
	.long	_ZTI5Base2          # typeinfo for Base2
	.long	_ZN5Base2D1Ev       # Base2::~Base2()
	.long	_ZN5Base2D0Ev       # Base2::~Base2()
	.long	_ZN5Base26mumbleEv  # Base2::mumble()
	.long	_ZNK5Base25cloneEv  # Base2::clone() const

              class Derived的虛表:

.weak	_ZTV7Derived
	.section	.rodata._ZTV7Derived,"aG",@progbits,_ZTV7Derived,comdat
	.align 32
	.type	_ZTV7Derived, @object
	.size	_ZTV7Derived, 48
_ZTV7Derived:                                  # vtable for Derived
	.long	0
	.long	_ZTI7Derived                   # typeinfo for Derived
	.long	_ZN7DerivedD1Ev                # Derived::~Derived()
	.long	_ZN7DerivedD0Ev                # Derived::~Derived()
	.long	_ZN5Base112speakClearlyEv      # Base1::speakClearly()
	.long	_ZNK7Derived5cloneEv           # Derived::clone() const
	.long	-8
	.long	_ZTI7Derived                   # typeinfo for Derived
	.long	_ZThn8_N7DerivedD1Ev           # non-virtual thunk to Derived::~Derived()
	.long	_ZThn8_N7DerivedD0Ev           # non-virtual thunk to Derived::~Derived()
	.long	_ZN5Base26mumbleEv             # Base2::mumble()
	.long	_ZTchn8_h8_NK7Derived5cloneEv  # covariant return thunk to Derived::clone() const

            因而當你將一個Derived對象地址指定給一個Base1指針或Derived指針時,被處理的

virtual table是主要表格。而當你將一個Derived對象地址指定給一個Base2指針時,被處理的

virtual table是次要表格。

             因爲執行期連接器(runtime linkers)的降臨(能夠支持動態共享函數庫),符號名稱的

連接可能變得很是緩慢。爲了調節執行期鏈接器的效率,Sun編譯器將多個virtual tables連鎖爲

一個:指向次要表格的指針,可由主要表格名稱加上一個offset得到。這樣的策略下,每個

class只有一個具名的virtual table。

              有三種狀況,第二或後繼的base class會影響對virtual functions的支持。第一種狀況

是,經過一個「指向第二個base class」的指針,調用derived class virtual function。例如:

Base2 *ptr = new Derived;

// 調整Derived::~Derived
// ptr必須被向後調整sizeof( Base1 )個bytes
delete ptr;

             這個操做的重點:ptr指向Derived對象中的Base2 subobject;爲了可以正確執行,ptr必須

調整指向Derived對象的起始處。

             第二種狀況是第一種狀況的變化,經過一個「指向derived class」的指針,調用第二個

base class中一個繼承而來的virtual function。在此狀況下,derived class指針必須再次調整,

以指向第二個base subobject。例如:

Derived *pder = new Derived;

// 調用Base2::mumble()
// pder必須被向前調整sizeof( Base1 )個bytes
pder->mumble();

            第三種狀況發生於一個語言擴充性質之下:容許一個virtual function的返回值類型有所變

化,多是base type,也多是publicly derived type。這一點能夠經由Derived::clone()函數實

例來講明。clone函數的Derived版本回傳一個Derived class指針,默默地改寫了它的兩個base

class函數實例。當咱們經過「指向第二個base class」的指針來調用clone()時,this指針的offset問

題因而誕生了:

Base2 *pb1 = new Derived;

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

          當進行pb1->clone()時,pb1會被調整指向Derived對象的起始地址,因而clone()的

Derived版會被調用,它會傳回一個指針,指向一個新的Derived對象;該對象的地址在被指定給

pb2以前,必須先通過調整,以指向Base2 subobject。

          Microsoft以所謂的「address points「來代替thunk策略。即將用來改寫別人的那個函數

(overriding function)期待得到的是」引入該virtual function之class「(而非derived class)的地

址。這就是該函數的「address point」。

          二、虛擬繼承下的Virtual Functions

          考慮下面的virtual base class派生體系,從Point2d派生出Point3d:

#include <iostream>

class Point2d
{
  public:
    Point2d( float x = 0.0, float y = 0.0 )
            : _x( x ), _y( y ) { }
    virtual ~Point2d() { }
  
  virtual void mumble( ) { _y += _x; }
  virtual float z() { return _x + _y; }

  protected:
    float _x, _y;
};

class Point3d : public virtual Point2d
{
  public:
    Point3d( float x = 0.0, float y = 0.0, float z = 0.0 )
            : Point2d( x, y ), _z( z ) { }
    ~Point3d() { }
  
  float z() { return _z; }

  protected:
    float _z;
};

int main()
{
  Point2d point2d;
  Point3d point3d;

  std::cout << "sizeof( point2d ) = " << sizeof( point2d ) << std::endl;
  std::cout << "sizeof( point3d ) = " << sizeof( point3d ) << std::endl; 

  return 0;
}

         class Point2d的虛表:

.weak	_ZTV7Point2d
	.section	.rodata._ZTV7Point2d,"aG",@progbits,_ZTV7Point2d,comdat
	.align 8
	.type	_ZTV7Point2d, @object
	.size	_ZTV7Point2d, 24
_ZTV7Point2d:                         # vtable for Point2d
	.long	0
	.long	_ZTI7Point2d          # typeinfo for Point2d
	.long	_ZN7Point2dD1Ev       # Point2d::~Point2d()
	.long	_ZN7Point2dD0Ev       # Point2d::~Point2d()
	.long	_ZN7Point2d6mumbleEv  # Point2d::mumble()
	.long	_ZN7Point2d1zEv       # Point2d::z()

          class Point3d的虛表:

.weak	_ZTV7Point3d
	.section	.rodata._ZTV7Point3d,"aG",@progbits,_ZTV7Point3d,comdat
	.align 32
	.type	_ZTV7Point3d, @object
	.size	_ZTV7Point3d, 60
_ZTV7Point3d:                            # vtable for Point3d
	.long	8
	.long	0
	.long	_ZTI7Point3d             # typeinfo for Point3d
	.long	_ZN7Point3dD1Ev          # Point3d::~Point3d()
	.long	_ZN7Point3dD0Ev          # Point3d::~Point3d()
	.long	_ZN7Point3d1zEv          # Point3d::z()
	.long	-8
	.long	0
	.long	-8
	.long	-8
	.long	_ZTI7Point3d             # typeinfo for Point3d
	.long	_ZTv0_n12_N7Point3dD1Ev  # virtual thunk to Point3d::~Point3d()
	.long	_ZTv0_n12_N7Point3dD0Ev  # virtual thunk to Point3d::~Point3d()
	.long	_ZN7Point2d6mumbleEv     # Point2d::mumble()
	.long	_ZTv0_n20_N7Point3d1zEv  # virtual thunk to Point3d::z()

         雖然Point3d有惟一一個(同時也是最左邊的)base class,也就是Point2d,單Point3d和

Point2d的起始部分並不像」非虛擬的單一繼承「狀況那樣一致。因爲Point2d和Point3d的對象不

再相符,二者之間的轉換也就須要調整this指針。至於在虛擬繼承的狀況下要消除thunks,通常

而言已經被證實是一項該難度技術。

         建議是不要在一個virtual base class中聲明nonstatic data members。要否則會愈來愈復

雜。

3、函數的效能

      在下面這組測試中,計算兩個3D點,其中用到一個nonmember friend function,

一個member function,以及一個virtual member function,而且Virtual member

function分別在單1、虛擬、多重繼承三種狀況下執行。

        對於nonmember function:

        未優化:

          優化:

        對於inline member:

          未優化:

 

             優化:

          對於static Member:

             未優化:

               優化:

          對於nonstatic Member:

          未優化:

 

               優化:

            對於Virtual Member:

             未優化:

                優化:

            對於Virtual Member(多重繼承):

             未優化:

         

                優化:

            對於Virtual Member(虛擬繼承):

              未優化:

              優化:

        nonmember 、static member或nonstatic member函數都被轉化爲徹底相同的形式。因此三

者效率徹底相同。

         virtual member的效率相比前三項下降了4%到11%不等。

         多重繼承中的virtual function的調用利用thunk技術用掉了較多成本。

         而虛擬繼承花掉了最多的成本。

         下面使用兩種方法優化:

         1)在函數參數中加上一個對象,用以存放加法的結果:

void Point3d::cross_product( Point3d &pC, const Point3d &pA, const Point3d &pB )
{

  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;
}

                  

         能夠看到在未優化狀況下,效率優化了50%。

        2)直接在this對象中計算結果:

void Point3d::cross_product( const Point3d &pB )
{

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

4、指向Member Function的指針(Pointer-to-Member Functions)

     取一個nonstatic data member的地址,獲得的結果是該member在class佈局中的

bytes位置(再加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 pointer to member
();        // argument list

       而後咱們能夠這樣定義並初始化該指針:

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

       也能夠這樣指定其值:

coord = &Point::y;

       欲調用它,能夠這麼作:

( origin.*coord )();

       或

( ptr->*coord )();

       這些操做會被編譯器轉化爲:

// 虛擬C++碼
( coord )( &origin );

       和

// 虛擬C++碼
( coord )( ptr );

        指向member function的指針的聲明語法,以及指向」member selection運算符「的指針,其

做用是做爲this指針的空間保存者。這也就是爲何static member functions(沒有this指針)的

類型是」函數指針」,而不是「指向member function的指針」之故。

         使用一個「member function指針」,若是並不用於virtual function、多重繼承、virtual base

class等狀況的話,並不會比使用一個「nonmember function指針」的成本高。上述三種狀況對於

「member function指針」的類型以及調用都太過複雜。事實上,對於那些沒有virtual functions、

virtual base class或multiple base classes的classes而言,編譯器能夠爲它們提供相同的效率。

        一、支持「指向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之指針」的狀況運行。

           對一個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;

            取x()或y()的地址:

&Point::x();
&Point::y();

             獲得的則是函數在內存中的地址,由於它們不是virtual。取z()的地址:

&Point::z();

             獲得的結果是2。經過pmf來調用z(),會被內部轉化爲一個編譯時期的式子,通常形式如

下:

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

              對一個「指向member function的指針」評估求值,會由於該值有兩種意義而複雜化:其調

用操做也將有別於常規調用操做。pmf的內部定義,也就是:

float ( Point::*pmf )();

               必須容許此函數可以尋址出nonvirtual x()和virtual z()兩個member functions,而那兩個

函數有着相同的原型:

// 二者均可以被指定給pmf
float Point::x() { return _x; }
float Point::z() { return 0; }

               只不過其中一個表明內存地址,另外一個表明virtual table中的索引值。所以,編譯器必

須定義pmf。使它可以(1)持有兩種數值,(2)更重要的是其數值能夠被區別表明內存地址還

是Virtual table中的索引值。

                在cfront2.0非正式版中,這兩個值被內含在一個普通的指針內。cfront如何識別該值是

內存地址仍是virtual table索引呢?它使用瞭如下技巧:

( ( ( int )pmf ) & ~127 )
  ?                          // non-virtual invocation
  ( *pmf )( ptr )
  :                          // virtual invocation
  ( *ptr->vptr[ ( int )pmf ]( ptr ) );

        二、在多重繼承之下,指向 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地址(爲

了方便,當index不指向virtual table時,會被設爲-1)。在此模型之下,像這樣的調用操做:
 

( 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或nonvirtual函數的調用操做透明化,vcall thunk會選出並調用相關

virtual table中的適當slot。

            這個結構體的另外一個反作用就是,當傳遞一個不變值的指針給member function時,它需

要產生一個臨時性對象。以下:

extern Point3d foo( const Point3d&, Point3d ( Point3d::* )() );
void bar( const Point3d& p )
{
  Point3d pt = foo( p, &Point3d::normal );
  // ...
}

           其中&Point3d::normal的值相似這樣:

{ 0, -1, 10727417 }

           將須要產生一個臨時性對象,有明確的初值:

// 虛擬C++碼
_mpter temp = { 0, -1, 10727417 }

foo( p, temp );

             delta字段表示this指針的offset值,而v_offset字段放的是一個virtual(或多重繼承中的第

二或後繼的)base class的vptr位置。若是ptr被編譯器放在class對象的起頭處,這個字段就沒

有必要了,代價則是C對象兼容性下降。這些字段只在多重繼承或虛擬繼承的狀況下才有其必要

性,有許多編譯器在自身內部根據不一樣的classes特性提供多種指向member functions的指針形

式,例如Microsoft就提供了三種風味:

            1)一個單一繼承實例(其中持有vcall thunk地址或是函數地址)

             2)一個多重繼承實例(其中持有faddr和delta兩個members)

             3)一個虛擬繼承實例(其中持有4個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;

5、Inline Functions

       下面是一個加法運算符的可能實現內容:

class Point
{
  friend Point operator+( const Point&, const Point& );
}

Point operator+( const Point &lhs, const Point &rhs )
{
  Point new_pt;
  
  new_pt._x = lhs._x + rhs._x;
  new_pt._y = lhs._y + rhs._y;

  return new_pt;
}

       理論上,一個比較「乾淨」的作法是使用inline函數來完成set和get函數:

// void Point::x( float new_ ) { _x = new_x; }
// float Point::x() { return _x; }

new_pt.x( lhs.x() + rhs.x() );

        因爲咱們受限只能在上述兩個函數中對_x直接存取,所以也就將稍後可能發生的data

members的改變所帶來的衝擊最小化了。若是把這些存取函數聲明爲inline,咱們就能夠繼續保

持直接存取members的那種高效率——雖然咱們亦兼顧了函數的封裝性。此外,加法運算符不

再須要被聲明爲Point的一個friend。

        然而,實際上咱們並不可以強迫將任何函數都變爲inline。關鍵詞inline只是一項請求。如

果這項請求被接受,編譯器就必須認爲它能夠用一個表達式(expression)合理地將這個函數

擴展開來。

        通常而言,處理一個inline函數,有兩個階段:

       1)分析函數定義,以決定函數的「intrinsic inline ability」(本質的inline能力)。「instrinsic」

一詞在這裏指「與編譯器相關」。

             若是函數因其複雜度,或因其建構問題,被判斷不可成爲inline,它會被轉爲一個static

函數,並在編譯模塊內產生對應的函數定義。

       2)真正的inline函數擴展操做是在調用的那一點上。這會帶來參數的求值操做以及臨時對象

的管理。

       一、形式參數(Formal Arguments)

       在inline擴展期間,每個形式參數都會被對應的實際參數取代。通常而言,面對「會帶來副

做用的實際參數」,一般都須要引入臨時性對象。換句話說,若是實際參數是一個常量表達式

(constant expression),咱們能夠在替換以前先完成其求值操做;後繼的inline替換,就能夠把

常量直接「綁」上去。若是既不是常量表達式,也不是個帶有反作用的表達式,那麼就直接帶換

之。

       假設有如下的簡單inline函數:

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

      下面是三個調用操做:

inline int
bar()
{
  int minval;
  int val1 = 1024;
  int val2 = 2048;

/* (1) */ minval = min( val1, val2 );
/* (2) */ minval = min( 1024, 2048 );
/* (3) */ minval = min( foo(), bar()+1 );

  return minval;
}

        標示爲(1)的那一行會被擴展爲:

// (1)參數直接替換
minval = val1 < val2 ? val1 : val2;

       標示爲(2)的那一行直接擁抱常量:

// (2) 代換以後,直接擁抱常量
minval = 1024;

       表示爲(3)的那一行則引起參數的反作用。它須要導入一個臨時性對象,以免重複求

值:

// (3) 有反作用,因此導入臨時性對象
int t1;
int t2;

minval = ( t1 = foo() ), ( t2 = bar() + 1 ),
           t1 < t2 ? t1 : t2;

       二、局部變量(Local Variables)

       若是咱們輕微地改變定義,在inline定義中加入一個局部變量,會怎樣?

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

        這個局部變量須要什麼額外的支持或處理嗎?若是咱們有如下的調用操做:

{
  int local_var;
  int minval;

  // ...
  minval = min( val1, val2 );
}

        inline被擴展來後,爲了維護其局部變量,可能會成爲這個樣子

{
  int local_var;
  int minval;

  // 將inline函數的局部變量處以「mangling」操做
  int _min_lv_minval;
  minval = 
    ( _min_lv_minval = 
      val1 < val2 ? val1 : val2 ),
      _min_lv_minval;
}

         通常而言,inline函數中的每個局部變量都必須被放在函數調用的一個封閉區段中,擁有

一個獨一無二的名稱。若是inline函數以單一表達式擴展屢次,則每次擴展都須要本身的一組局

部變量。若是inline函數以分離的多個式子(duscrete statements)被擴展屢次,那麼只須要一

組局部變量,就能夠重複使用。

         inline函數中的局部變量,再加上有反作用的參數,可能會致使大量臨時性對象的產生。特

別是若是它以單一表達式被擴展屢次的話。例如:

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

         可能被擴展爲:

// 爲局部變量產生臨時變量
int _min_lv_minval_00;
int _min_lv_minval_01;

// 爲放置反作用值而產生臨時變量
int t1;
int t2;

minval =
  ( ( _min_lv_minval_00 = 
      val1 < val2 ? val1 : val2 ),
      _min_lv_minval_00 )
  +
  ( ( _min_lv_minval_01 = ( t1 = foo() ),
      ( t2 = foo() + 1 ),
      t1 < t2 ? t1 : t2 ),
      _min_lv_minval_01 );

        Inline函數對於封裝提供了一種必要的支持,能夠有效存取封裝於class中的nonpublic數

據。它同時也是C程序中大量使用的#define(前置處理宏)的一個安全代替品——特別是若是

宏中的參數有反作用的話。然而一個inline函數若是被調用太屢次的話,會產生大量的擴展碼,

使程序大小暴漲。

       對於既要安全又要效率的程序碼,inline函數提供了一個強而有力的工具。然而,與non-

inline函數比起來,它們須要更加當心地處理。            

相關文章
相關標籤/搜索