理解虚函数有困难

时间:2013-07-31 03:22:47

标签: c++

#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,因此会调用该函数吗?但是如何在运行时实现呢?

3 个答案:

答案 0 :(得分:6)

好的,第一个问题:我举一个可能更好理解的例子! enter image description here

#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)

  1. 除非您确定自己在做什么,否则不应尝试访问vtable指针。就语言而言,通常是我们用来定义程序含义的语言,甚至不存在vtable。它是一个实现细节,它属于实现(a.k.a.编译器和运行时环境)。

    如果实现受到便携式ABI(应用程序二进制接口)的约束,那么ABI将说明在哪里找到vtable指针以及vtable内部的内容。 reinterpret_cast< vtable const * const & >( my_obj )应该做的是从任何“合理的”ABI上的对象获取指针。

    这样的程序将被限制在一个平台和一个ABI上。 (C ++ ABI接口往往比C更频繁地更改,但比其他语言更少。)取决于ABI是一个糟糕的设计选择,除非你只是想证明你疯了。

  2. 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])(...)

  

另一个问题,与我需要清理的虚拟功能无关。如果函数具有与任何其他变量一样的地址,为什么它们不占用对象中的空间?

他们为什么要这样?这意味着每个对象中的每个函数的副本。有什么意义呢?