#include<iostream>
using namespace std;
class X
{
int a;
int b;
public:
void f(int a)
{
cout<<"\nInside X";
}
virtual void abc ()
{
cout<<"\nHello X";
}
};
class Y : public X
{
int a;
public:
void f(int a, int b)
{
cout<<"\nInside Y";
}
void abc()
{
cout<<"\nHello Y";
}
};
int main()
{
X a;
cout<<sizeof(X);
Y b;
cout<<sizeof(Y);
X *h = new Y;
h->abc();
}
据我所知,类X的大小为12个字节的原因是因为它包含一个到虚拟表的vptr(虚拟指针)。无论如何我可以读取这个虚拟表,如果没有,至少可以访问虚拟指针。我尝试使用工会,但它给了我一些错误。
另外,当我调用h-&gt; abc()时,它如何知道类的对象,h
指向哪个?我认为大部分都是在编译时完成的。但是当你有一个指向派生类的基类指针时,它如何知道要执行哪个类函数。
考虑这两种情况
X *h = new X;
h->abc();/* This would call the abc function in X */
和
X *h = new Y;
h->abc();/* This would call the abc function in Y*/
我读过,h
指针会转到它指向的对象的vtable,因此会调用该函数吗?但是如何在运行时实现呢?
答案 0 :(得分:6)
好的,第一个问题:我举一个可能更好理解的例子!
#include<iostream>
using namespace std;
class Base1 {
public:
int ibase1;
Base1():ibase1(10) {}
virtual void f() { cout << "Base1::f()" << endl; }
virtual void g() { cout << "Base1::g()" << endl; }
virtual void h() { cout << "Base1::h()" << endl; }
};
class Base2 {
public:
int ibase2;
Base2():ibase2(20) {}
virtual void f() { cout << "Base2::f()" << endl; }
virtual void g() { cout << "Base2::g()" << endl; }
virtual void h() { cout << "Base2::h()" << endl; }
};
class Base3 {
public:
int ibase3;
Base3():ibase3(30) {}
virtual void f() { cout << "Base3::f()" << endl; }
virtual void g() { cout << "Base3::g()" << endl; }
virtual void h() { cout << "Base3::h()" << endl; }
};
class Derive : public Base1, public Base2, public Base3 {
public:
int iderive;
Derive():iderive(100) {}
virtual void f() { cout << "Derive::f()" << endl; }
virtual void g1() { cout << "Derive::g1()" << endl; }
};
这是一个派生类的内存等级,它实现了三个基类base1,base2,base3,你在哪里:
Base1 *p1 = new Derive();
Base2 *p2 = new Derive();
Base3 *p3 = new Derive();
p1将指向vtale1,p2将指向vtable2,p3将指向vtable3,如果调用某个虚函数,它将找到非常虚拟的表并获取地址!
在您的代码中:
X *h = new Y;
h将指向Y的内存的起始位置,即X的虚拟表,他将找到在Y中实现的abc()
的地址!
你的第二个问题:
编译器将成员函数视为普通函数,因此它将成员函数的地址放在code section
中,因此它不占用内存!!
如果你想阅读虚拟表,你可以尝试这样:我在gcc4.7的例子中尝试过这个
typedef void(*Func)(void);
Derive d;
int **pd = (int **)(&d);
int i = 0;
while(i < 4)
{
Func f = (Func)pd[0][i];
f();
i++;
}
int s = (int)(pd[1]);
cout << s << endl;
i = 0;
cout << "===============================================" << endl;
while(i < 3)
{
Func f = (Func)pd[2][i];
f();
i++;
}
s = (int)(pd[3]);
cout << s << endl;
cout << "===============================================" << endl;
i = 0;
while(i < 3)
{
Func f = (Func)pd[4][i];
f();
i++;
}
s = (int)(pd[5]);
cout << s << endl;
s = (int)(pd[6]);
cout << s << endl;
您将得到如下结果:
Derive::f()
Base1::g()
Base1::h()
Derive::g1()
10
===============================================
Derive::f()
Base2::g()
Base2::h()
20
===============================================
Derive::f()
Base3::g()
Base3::h()
30
100
答案 1 :(得分:2)
除非您确定自己在做什么,否则不应尝试访问vtable指针。就语言而言,通常是我们用来定义程序含义的语言,甚至不存在vtable。它是一个实现细节,它属于实现(a.k.a.编译器和运行时环境)。
如果实现受到便携式ABI(应用程序二进制接口)的约束,那么ABI将说明在哪里找到vtable指针以及vtable内部的内容。 reinterpret_cast< vtable const * const & >( my_obj )
应该做的是从任何“合理的”ABI上的对象获取指针。
这样的程序将被限制在一个平台和一个ABI上。 (C ++ ABI接口往往比C更频繁地更改,但比其他语言更少。)取决于ABI是一个糟糕的设计选择,除非你只是想证明你疯了。
vtable标识派生类 - 这是它的目的。它包含指向由派生类覆盖基类实现的函数的指针。它还包含一个结构,其中包含派生类的名称以及指向其基础的链接,以便动态确定派生类的来源。
dynamic_cast
用于确定派生和查找派生对象的算法实际上可能会出乎意料地慢 - 而不是O(1)。它通常必须搜索基类的链接结构。
答案 2 :(得分:1)
无论如何我可以阅读这个虚拟表
不是真的,不知道指针相对于对象指针值的位置,这是特定于编译器的。
如果没有,至少可以访问虚拟指针。
为什么呢?您可以通过h->abc
获取该功能的地址,这是您想要的吗?
另外,当我调用h-&gt; abc()时,它如何知道类的对象,h指向?
它并不是真的,它只知道该类的vtable在哪里。如果使用RTTI,则vtable中有信息告诉它什么类,但调用虚函数不是必需的。从X派生的每个类都有自己的vtable,包含自己的虚函数指针。 (当然,总是假设基于vtable的实现。)
我读过,h指针会转到它指向的对象的vtable,因此会调用该函数吗?但是如何在运行时实现呢?
你刚才自己描述过。为了略微详细说明,指针h->abc
解析为h->_vtable[x]
一些常量x
,表示abc
虚函数指针的vtable中的偏移量。因此,通话会解析为*(h->_vtable[abc])(...)
。
另一个问题,与我需要清理的虚拟功能无关。如果函数具有与任何其他变量一样的地址,为什么它们不占用对象中的空间?
他们为什么要这样?这意味着每个对象中的每个函数的副本。有什么意义呢?