關於pointer-to-member的一些討論

今天爲了測試data member pointer,在BCB6裏寫了一段程序,卻發現了一點問題。
代碼很簡單:

struct Base1 { int val1;};

printf("&Base1::val1 = %p, %d/n", &Base1::val1, 2);


結果發現數字2 輸出失敗,顯示爲0,察看CPU發現:
0040116B 6A02             push 0x02
0040116D FF35A8204000     push dword ptr [0x4020A8]
00401173 FF35A4204000     push dword ptr [0x4020A4]
00401179 68D4204000       push 0x004020d4
0040117E E831020000       call CC3260MT._printf
00401183 83C410           add esp,0x10

原來第一個參數“兩次”被push入棧,導致printf認爲第一次push的就是%d的參數。將上面的程序改爲:

printf("&Base1::val1 = %p, %d %d/n", &Base1::val1, 2);

後可以發現2的輸出。

爲什麼會將pointer入棧兩次?

----------------------------------------------------

1/9/2006

通過一個簡單的測試:

int Base1::*pp;

printf("%d/n",sizeof(pp));

發現在BCB中是8字節(即使將Data Alignment設置成word或者double word也沒有區別),而在VC或者GNU C++中是4字節。

謝謝某鳥的解釋,於是查了一下__closure。讓人驚訝的是他可以讓一個“普通”的函數指針指向“thiscall”調用的成員函數(Using a closure, you can get a pointer to member function for an object (i.e. a particular instance of a class). The object can be any object, regardless of its inheritance hierarchy. The object抯 this pointer is automatically used when calling the member function through the closure. )。看來多出來的4字節是用來記錄this指針了。(結果也很好證實)

不過:

[問題一]

爲什麼data member pointer也會多這4字節?用來做什麼?還是隻是單純的爲了統一?

試了幾個case,包括虛擬繼承,發現“後面”的4字節都是0。本來以爲是記錄繼承類中基類的偏移量,發現不是。又以爲和上面一樣與this指針有關係,不過,data member pointer用this指針做什麼?後來猜想爲了決議二義性,但因爲下面的問題,測試未遂。

[問題二]

本來想做另外一個實驗:

struct Base1 { int val1; };

struct Base2: Base1 { int val2;};

struct Base3: Base1 { int val3;};

struct Derived : Base2, Base3 {int val4;};


如果定義一個Derived d; 顯然可以通過d.Base2::val1和d.Base3::val1來分別訪問兩個val1,但如果是data member pointer呢?如何解決二義性,用一個int Base1::*pp;如何分別訪問兩個val1?

我試了

pp = &Derived::Base2::val1;
pp = &Derived::Base2.val1;
pp = &Derived.Base2::val1;
pp = &Derived.Base2.val1;

都不行。

----------------------------------------------------

1/13/2006

本來只想把問題侷限到BCB中,不過既然不停的提到了VC中data member pointer。我也說說自己的看法。

一如ICOM(《Incide The C++ Object Model》,《深入探索C++對象模型》)中說,一般data member pointer用offset+1,這樣可以避免以下的混亂。

int Base1::*p1 = 0;
int Base1::*p2 = &Base1::val1;


但是在VC中直接用的是offset。VC中避免這種的混亂的方法是int Base1::*p1 = 0;這樣語句,實際上把p1置成了0xFFFFFFFFH,而非像普通pointer一樣的0。判斷

if (p1 == 0)

被轉換成爲 cmp dword ptr[...], 0FFFFFFFFh

相當於說,BCB/GNU data member pointer的值 = VC data member pointer的值 + 1

----------------------------------------------------

2/7/2006


1 __closure
關於BCB中的__closure不多說了,將online help中的說明拷貝如下:
The __closure keyword is used to declare a special type of pointer to a member function. In standard C++, the only way to get a pointer to a member function is to use the fully qualified member name, as shown in the following example:

class base

{
  public:
    void func(int x) { };
};
typedef void (base::* pBaseMember)(int);
int main(int argc, char* argv[])
{
  base        baseObject;
  pBaseMember m = &base::func; // Get pointer to member 'func'
  // Call 'func' through the pointer to member
  (baseObject.*m)(17);
  return 0;
}

However, you cannot assign a pointer to a member of a derived class to a pointer to a member of a base class. This rule (called contravariance) is illustrated in the following example:

class derived: public base

{
  public:
    void new_func(int i) { };
};
int main(int argc, char* argv[])
{
  derived         derivedObject;
  pBaseMember m = &derived::new_func; // ILLEGAL
  return 0;
}

The __closure keyword extension allows you to skirt this limitation, and more. Using a closure, you can get a pointer to member function for an object (i.e. a particular instance of a class). The object can be any object, regardless of its inheritance hierarchy. The object抯 this pointer is automatically used when calling the member function through the closure. The following example shows how to declare and use a closure. The base and derived classes provided earlier are assumed to be defined.

int main(int argc, char* argv[])

{
  derived         derivedObject;
  void (__closure *derivedClosure)(int);
     derivedClosure = derivedObject.new_func; // Get a pointer to the 'new_func' member.
                                              // Note the closure is associated with the
                                              // particular object, 'derivedObject'.
     derivedClosure(3);  // Call 'new_func' through the closure.
     return 0;
}

Closures also work with pointers to objects, as illustrated in this example:

void func1(base *pObj)

{
  // A closure taking an int argument and returning void.
  void ( __closure *myClosure )(int);

  // Initialize the closure.

  myClosure = pObj->func;

  // Use the closure to call the member function.

  myClosure(1);
  return;
}

int main(int argc, char* argv[])

{
  derived         derivedObject;
  void (__closure *derivedClosure)(int);
     derivedClosure = derivedObject.new_func; // Same as before...
     derivedClosure(3); 
     // We can use pointers to initialize a closure, too.
     // We can also get a pointer to the 'func' member function
     // in the base class.
     func1(&derivedObject);
     return 0;
}

Notice that we are passing a pointer to an instance of the derived class, and we are using it to get a pointer to a member function in the base class - something standard C++ does not allow us to do.
值得注意的是
1 BCB這樣處理pointer to member function的確是和C++標準中的contravariance rule有悖的;
2 這個關鍵字主要是用於pointer to member function而不是pointer to data member。所以BCB對於data member pointer的實現應該還是沒有違背C++標準。
關於__closure這個問題扯遠了,以後再聊。不過前面提到的兩個問題還是沒有答案。

2 關於printf中的%p描述符。
按C99標準所說
1 The argument shall be a pointer to void. The value of the pointer is converted to a sequence of printing characters, in an implementation-defined manner.
2 If a conversion specification is invalid, the behavior is undefined. If any argument is not the correct type for the corresponding conversion specification, the behavior is
undefined.

可見1)%p的參數必須是void *; 2)%p的輸出是基於實現的; 3)任何不能成功轉換,printf的輸出是UB。
因此,就上例中的data member pointer,Borland對%p的輸出無論怎麼亂都不算是違反標準。當然,既然是UB,也不存在bug之說,這點我同意。

如果用C++中的cout來顯示可以得到正確的顯示,這再次說明了《Effecitve C++》中的觀點--儘量用C++的標準輸入/輸出
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章