由于不同C++编译器对C++对象模型的结构可能是不一样的。比如说,VC++系列是将虚函数表指针放在对象首地址,而GCC系列的编译器则是将虚函数表指针放在对象地址尾部。
那么下面我将详细讲讲GCC编译器下的对象模型(含虚函数表指针)。
先看下面这个例子:
using namespace std;
class A
... {
private:
int a;
public:
A(void) : a(0)
...{
}
virtual void Hello(void)
...{
cout << "Hello, world!" << endl;
}
} ;
class B : public A
... {
private:
int b;
public:
B(void) : b(1)
...{
}
virtual void Hello(void)
...{
cout << "B!" << endl;
}
} ;
上面的示例中,A对象的地址中存放两个4字节的数据,一个是变量a,另一个是虚函数表指针。变量a放在低地址;虚函数表指针放在高地址,那么方便起见,我这样表示——A objA = { a, vptr }。而B的对象首先是存放类A域的所有成员,然后是B自己的数据成员——B objB = { a, vptr, b }。
那么这样的安排就有助于编译器处理这样的情况了——
A * p = & b;
b -> Hello();
那么如果是多继承或是虚继承会怎样呢?
碰到这种情况,GCC或其兼容C++编译器会同时判断左操作数的类型以及有操作数的类型。根据左操作数的类型来判断左操作数的指针指向有操作数对象的哪个偏移地址。如果是其他类型的指针(如void*),则指向对象的首地址。
下面请看一下详细的测试代码:
using namespace std;
class A
... {
private:
int a;
public:
A(void) : a(0)
...{
}
virtual void Hello(void)
...{
cout << "Hello, world!" << endl;
}
} ;
class AA
... {
private:
int aa;
public:
AA(void) : aa(100)
...{
}
virtual void Hi(void)
...{
cout << "Hi, therte!" << endl;
}
} ;
class B : public A
... {
private:
int b;
public:
B(void) : b(1)
...{
}
virtual void Hello(void)
...{
cout << "B!" << endl;
}
} ;
class C : virtual public A
... {
private:
int c, d;
public :
C(void) : c(2), d(3)
...{
}
void Hello(void)
...{
cout << "C!" << endl;
}
} ;
class E : public A, public AA
... {
private:
int e;
public:
E(void) : e(8)
...{
}
void Hello(void)
...{
cout << "E!" << endl;
}
void Hi(void)
...{
cout << "Hi!" << endl;
}
} ;
extern " C " void test( void );
int main( void )
... {
A a;
B b;
C c;
E e;
unsigned long s[10];
int i=0;
for(; i<sizeof(a) >> 2; i++)
s[i] = ((unsigned long*)&a)[i];
a.Hello();
for(i=0; i<sizeof(b) >> 2; i++)
s[i] = ((unsigned long*)&b)[i];
A *p = &b;
p->Hello();
for(i=0; i<sizeof(c) >> 2; i++)
s[i] = ((unsigned long*)&c)[i];
p = &c;
void* q = (C*)&c;
p->Hello();
for(i=0; i<sizeof(e) >> 2; i++)
s[i] = ((unsigned long*)&e)[i];
AA *pp = &e;
p = &e;
q = &e;
return 0;
}
在main函数中,上面的p和q两个指针的值是不同的,尽管它们指向同一个对象。p指向了对象c的A类域的偏移处;而q则是指向了p 的首地址。那么下面的pp和p及q也是不同,这里的p和q都是指向首地址,因为类A域在对象e的起始处,而pp则是指向了e的AA类域的偏移处。
大家可以利用以上代码进行调试,有个感性认识。
所以在C++中,甚用void*指针指向一个对象,否则当再次进行类型转换时,调用相关函数可能会发生意想不到的情况。那么这个时候还是利用模板,通过范型来解决类型问题,这样做更安全,而且更优美。