今天为了测试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++的标准输入/输出。