C++基礎系列--指向數據成員的指針

指向數據成員的指針,是一個有點神祕又頗有用處的語言特性,特別是如果你需要詳細調查class members的底層佈局的話。這樣的調查可以用於決定vptr是放在class的起始處或者尾端。另外一個用途是可以用來決定class中的access sections的次序。

考慮下面的Point3d聲明。其中有一個virtual function,一個static data member,以及三個座標:

class Point3d{

public:

virtual ~Point3d();

//…

protected:

static Point3d origin;

float x,y,z;

}

每一個Point3d的對象含有三個座標值,依次爲x、y、z,以及一個vptr。至於靜態數據成員origin,將被放在class object之外。唯一可能因編譯器不同而不同的是vptr的位置。C++標準允許vptr被放在對象中的任何位置:在起始處,在尾端,或者是在各個members之間。然而實際上,所有編譯器不是把vptr放在對象的頭部,就是放在對象的尾部。

 

那麼,取某個座標成員的地址,代表什麼意思呢?例如,以下操作所得到的值代表什麼:

&Point3d::z;

上述操作將得到z座標在class object中的偏移量(offset)。最低限度其值將是x和y的大小總和,因爲C++語言要求同一個access level中的members的排列次序應該和其聲明次序相同。在一臺32位機器上,每一個float是4個字節,所以我們應該期望剛纔獲得的值要不是8,就是12(在32位機器上,一個vptr是4個字節)。

然而,這樣的期望還少了1個字節。對於C和C++程序員來說,這多少算是個有點年代的錯誤了。如果vptr放在對象的末尾,則三個座標值在對象佈局中的偏移量分別爲0、4、8;如果vptr放在對象的開頭,則三個座標值在對象佈局中的偏移量分別爲4、8、12。然而你若去取data members的地址,傳回的值總是多1,也就是1、5、9或5、9、12等等。

#include <iostream>

 

class Point3d{

public:

    virtual ~Point3d(){};

    //…

public://如果換成private或者protected,則報錯

    static Point3d origin;

    float x;

    float y;

    float z;

};

 

int main()

{

    printf("&Point3d::x = %p/n", &Point3d::x);

    printf("&Point3d::y = %p/n", &Point3d::y);

    printf("&Point3d::z = %p/n", &Point3d::z);

   

   std::cout<<"&Point3d::x = "<<&Point3d::x<<std::endl;

    std::cout<<"&Point3d::y = "<<&Point3d::y<<std::endl;

    std::cout<<"&Point3d::z = "<<&Point3d::z<<std::endl;

 

    return 0;

}

 

輸出結果爲:

&Point3d::x = 00000004

&Point3d::y = 00000008

&Point3d::z = 0000000C

&Point3d::x = 1

&Point3d::y = 1

&Point3d::z = 1

Press any key to continue

 

在vc6.0下,並沒有增加1,原因可能是visual c++做了特殊的處理。

在vc6.0下,通過printf或者cout的形式,都可以正常運行,只不過,得到的結果不一致。使用std::cout時,都輸出的是1,應該作何解釋呢?

以上程序,如果數據成員爲private或者protected的,則無法編譯通過,而書上的例子,卻是protected,作者的測試程序可能是怎樣的呢?

(以上程序在vc6.0,virsual studio2008,DEV-C++下測試過,與《深入探索C++對象模型》P131對應的說明有些出入)

爲啥傳回的值會多1個字節呢?這一個字節,主要用來區分“沒有指向任何數據成員的指針”和“指向第一個數據成員的指針”這兩種情況。考慮下面這樣的例子:

float Point3d::*p1 =0;

float Point3d::*p2 = &Point3d::x;

//Point3d::* 的意思是“指向Point3d data member”的指針類型

 

if( p1 == p2 ){

cout <<” p1 & p2 contain the same value.”;

cout <<”they must address the same member!”<<endl;

}

爲了區分p1和p2,每一個真正的member offset值都被加上1。因此,不論編譯器或者使用者都必須記住,在真正使用該值以指出一個member之前,請先減去1。

 

在充分認識“指向數據成員的指針”之後,要解釋:

&Point3d::z;和 &origin.z

之間的差異,就非常明確了:取一個非靜態數據成員的地址,將會得到它在class中的offset,取一個綁定於真正class object身上的數據成員的地址,將會得到該數據成員在內存中真正的地址。&origin.z的返回值類型應該是:float * 而不是:float Point3d::* 。

 

#include <iostream>

 

class Point3d{

public:

    virtual ~Point3d(){};

    //…

public:

    float x;

    float y;

    float z;

};

 

int main()

{

   Point3d origin;

   printf("&origin.z = %p/n", &origin.z);

 

    return 0;

}

輸出結果爲:

&origin.z = 0013FF7C

 

參考資料:

《深度探索C++對象模型》


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章