精华内容
下载资源
问答
  • 2、派生类中重新定义基类中定义的虚函数时,会告诉编译器不要 静态链接 到该函数,在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为 动态链接 ,或 后期绑定。 3、虚函数必须是 非静态 的...

    虚函数

    1、虚函数 :是在基类中使用关键字 virtual 声明的函数。

    2、派生类中重新定义基类中定义的虚函数时,会告诉编译器不要 静态链接 到该函数,在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为 动态链接 ,或 后期绑定

    3、虚函数必须是 非静态 的成员函数,虚函数经过派生之后,就可以实现运行过程中的 多态。
    4、虚函数有具体的代码实现过程,否则会报未定义的错误。
    5、虚函数的引入,主要是为了实现多态

    1、虚函数定义:

    class People
    {
    public:
        virtual void printf(void)
        {
            cout << "from class People" << endl;
        }
    };
    
    class Man : public People   //公有继承People类
    {
    public:
        void printf(void)
        {
            cout << "from Man class" << endl;
        }
    
    };
    
    int main(void)
    {
    	People peo;
    	Man man;
    	peo.printf();
    	man.printf();
    }
    
    输出结果:
    from class People
    from Man class
    

    2、虚函数表

    1、在一个类中,当至少存在一个虚函数的时候,在编译期间,编译器就会为类A生成一个虚函数表(virtual table )2、这个虚函数表会一直伴随着类A,经过编译、链接直到生成一个可执行文件。
    3、这个类A以及伴随类A的虚函数表,都会保存在整个可执行文件之中。
    4、在这个可执行文件执行的时候,也会被一并装载到内存中来。
    

    纯虚函数

    1、纯虚函数是一种特殊的虚函数,在一些情况下,虚函数不能实现的一些函数,需要在派生类
    中进行实现的函数,叫做纯虚函数。
    2、纯虚函数一般没有代码实现部分。如
    virtual void fun() = 0;

    class A{
    	public:
    	virtual void fun() = 0;//纯虚函数
    };
    

    纯虚函数表指针

    1、含有纯虚函数的类,被称为抽象类,而抽象类不能实例化对象。
    2、纯虚函数必须在子类中实现才可以调用,因为抽象基类中,纯虚函数在派生基类中只有声明,没有定义。
    3、当一个或多个虚函数加入类中之后,编译器就会向类中插入一个看不见的成员变量,在类中,这个看不见的成员变量就类似以下伪代码

       class A
        {
            public:
                void *vptr;  //虚函数表指针 (virtual table pointer)
                ……
        };
    

    这个看不见的成员变量有一个名字叫:虚函数表指针
    这个虚函数表指针正好是8个字节,而这8个字节正好占用类对象的内存空间的

    展开全文
  • 场景编译通过,但链接失败 如果连接时遇到undefined ...虚函数错误,发生在有继承关系的类之间。 父类有虚函数或纯虚函数,但子类没有实现这些函数。 析构函数为虚函数,但对应的实现析构函数没有实现。 ...

    场景编译通过,但链接失败

    如果连接时遇到undefined reference to 'vtable for ...

    1. 虚函数表错误,发生在有继承关系的类之间。
    2. 父类有虚函数或纯虚函数,但子类没有实现这些函数。
    3. 析构函数为虚函数,但对应的实现析构函数没有实现。
    展开全文
  • 原文链接:http://www.keepsimply.org/2012/07/11/cpp-vtable/ 作者:独酌逸醉 ...如果您发现了文中的错误,或者您有的不同的见解,可以给我留言或者给我发邮件,我们共同探 讨。如果您觉得我的文章

    原文链接:http://www.keepsimply.org/2012/07/11/cpp-vtable/

    作者:独酌逸醉
    时间:2012.07.11

    声明:
      本文内容由自互联网资源(见参考资料)、个人的一些 C++ 学习感悟、个人实践整理而成。文章仅以技术学习和交流为目的。如果您发现了文中的错误,或者您有的不同的见解,可以给我留言或者给我发邮件,我们共同探 讨。如果您觉得我的文章侵犯到您的权益,请联系我(chinajiezhang@gmail.com),以便我做相应的处理。最后,如需转载,可不必标明 出处。但一定要全文转载,保证参考链接的完整性,这是对别人写作的基本尊重。谢谢合作!

    写博缘由:
      1.对C++多态内部机制了解的渴望;
      2.眼过千遍,不如手过一遍;
      3.整理成文,帮助自己记忆;不求帮到他人,只求不会误导。


    一、背景知识(一些基本概念)

    虚函数(Virtual Function):在基类中声明为 virtual 并在一个或多个派生类中被重新定义的成员函数。
    纯虚函数(Pure Virtual Function):基类中没有实现体的虚函数称为纯虚函数(有纯虚函数的基类称为虚基类)。
    C++  “虚函数”的存在是为了实现面向对象中的“多态”,即父类类别的指针(或者引用)指向其子类的实例,然后通过父类的指针(或者引用)调用实际子类的成员函数。通过动态赋值,实现调用不同的子类的成员函数(动态绑定)。正是因为这种机制,把析构函数声明为“虚函数”可以防止在内存泄露。
    实例:

    复制代码
    #include <iostream>
    using namespace std;
    
    class base_class
    {
    public:
        base_class()
        {
        }
        virtual ~base_class()
        {
        }
    
        int normal_func()
        {
            cout << "This is  base_class's normal_func()" << endl;
            return 0;
        }
        virtual int virtual_fuc()
        {
            cout << "This is  base_class's virtual_fuc()" << endl;
            return 0;
        }
    
    };
    
    class drived_class1 : public base_class
    {
    public:
        drived_class1()
        {
        }
        virtual ~drived_class1()
        {
        }
    
        int normal_func()
        {
            cout << "This is  drived_class1's normal_func()" << endl;
            return 0;
        }
        virtual int virtual_fuc()
        {
            cout << "This is  drived_class1's virtual_fuc()" << endl;
            return 0;
        }
    };
    
    class drived_class2 : public base_class
    {
    public:
        drived_class2()
        {
        }
        virtual ~drived_class2()
        {
        }
    
        int normal_func()
        {
            cout << "This is  drived_class2's normal_func()" << endl;
            return 0;
        }
        virtual int virtual_fuc()
        {
            cout << "This is  drived_class2's virtual_fuc()" << endl;
            return 0;
        }
    };
    
    int main()
    {
        base_class * pbc = NULL;
        base_class bc;
        drived_class1 dc1;
        drived_class2 dc2;
    
        pbc = &bc;
        pbc->normal_func();
        pbc->virtual_fuc();
    
        pbc = &dc1;
        pbc->normal_func();
        pbc->virtual_fuc();
    
        pbc = &dc2;
        pbc->normal_func();
        pbc->virtual_fuc();
        return 0;
    }
    复制代码

    输出结果:

    复制代码
    This is  base_class's normal_func()
    This is  base_class's virtual_fuc()
    This is  base_class's normal_func()
    This is  drived_class1's virtual_fuc()
    This is  base_class's normal_func()
    This is  drived_class2's virtual_fuc()
    复制代码

     

    假如将 base_class 类中的 virtual_fuc() 写成下面这样(纯虚函数,虚基类):

    // 无实现体
    virtual int virtual_fuc() = 0;

    那么 virtual_fuc() 是一个纯虚函数,base_class 就是一个虚基类:不能实例化(就是不能用它来定义对象),只能声明指针或者引用。读者可以自行测试,这里不再给出实例。


    虚函数表(Virtual Table,V-Table):使用 V-Table 实现 C++ 的多态。在这个表中,主要是一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其真实反应实际的函数。这样,在有虚函数的类的实例中分配了指向这个表的指针的内存,所以,当用父类的指针来操作一个子类的时候,这张虚函数表就显得尤为重要了,它就像一个地图一样,指明了实际所应该调用的函数。
    编译器应该保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下)。 这意味着可以通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。

    二、无继承时的虚函数表

    复制代码
    #include <iostream>
    using namespace std;
    
    class base_class
    {
    public:
        virtual void v_func1()
        {
            cout << "This is base_class's v_func1()" << endl;
        }
        virtual void v_func2()
        {
            cout << "This is base_class's v_func2()" << endl;
        }
        virtual void v_func3()
        {
            cout << "This is base_class's v_func3()" << endl;
        }
    };
    
    int main()
    {
        // 查看 base_class 的虚函数表
        base_class bc;
        cout << "base_class 的虚函数表首地址为:" << (int*)&bc << endl; // 虚函数表地址存在对象的前四个字节
        cout << "base_class 的 第一个函数首地址:" << (int*)*(int*)&bc+0 << endl; // 指针运算看不懂?没关系,一会解释给你听
        cout << "base_class 的 第二个函数首地址:" << (int*)*(int*)&bc+1 << endl;
        cout << "base_class 的 第三个函数首地址:" << (int*)*(int*)&bc+2 << endl;
        cout << "base_class 的 结束标志: " << *((int*)*(int*)&bc+3) << endl;
        
        // 通过函数指针调用函数,验证正确性
        typedef void(*func_pointer)(void);
        func_pointer fp = NULL;
        fp = (func_pointer)*((int*)*(int*)&bc+0); // v_func1()
        fp();
        fp = (func_pointer)*((int*)*(int*)&bc+1); // v_func2()
        fp();
        fp = (func_pointer)*((int*)*(int*)&bc+2); // v_func3()
        fp();
        return 0;
    }
    复制代码

    输出结果:

    复制代码
    base_class 的虚函数表首地址为:0x22ff0c
    base_class 的 第一个函数首地址:0x472c98
    base_class 的 第二个函数首地址:0x472c9c
    base_class 的 第三个函数首地址:0x472ca0
    base_class 的虚函数表结束标志: 0
    This is base_class's v_func1()
    This is base_class's v_func2()
    This is base_class's v_func3()
    复制代码


    简单的解释一下代码中的指针转换:
    &bc:获得 bc 对象的地址
    (int*)&bc: 类型转换,获得虚函数表的首地址。这里使用 int* 的原因是函数指针的大小的 4byte,使用 int* 可以使得他们每次的偏移量保持一致(sizeof(int*) = 4,32-bit机器)。
    *(int*)&bc:解指针引用,获得虚函数表。
    (int*)*(int*)&bc+0:和上面相同的类型转换,获得虚函数表的第一个虚函数地址。
    (int*)*(int*)&bc+1:同上,获得第二个函数地址。
    (int*)*(int*)&bc+2:同上,获得第三个函数地址。
    *((int*)*(int*)&bc+3:获得虚函数表的结束标志,所以这里我解引用了。和我们使用链表的情况是一样的,虚函数表当然也需要一个结束标志。
    typedef void(*func_pointer)(void):定义一个函数指针,参数和返回值都是 void。
    *((int*)*(int*)&bc+0):找到第一个函数,注意这里需要解引用。
    对于指针的转换,我就解释这么多了。下面的文章,我不再做解释,相信大家可以举一反三。如果你觉得很费解的话,我不建议继续去看这篇文章了,建议你去补一补基础(《C和指针》是一本很好的选择哦!)。

    通过上面的例子的尝试和输出结果,我们可以得出下面的布局图示:

    三、单一继承下的虚函数表
    3.1子类没有父类的虚函数(陈皓文章中用了“覆盖”一词,我觉得太合理,但是我又找不到更合理的词语,所以就用一个句子代替了。^-^)

    复制代码
    #include <iostream>
    using namespace std;
    
    class base_class
    {
    public:
        virtual void v_func1()
        {
            cout << "This is base_class's v_func1()" << endl;
        }
        virtual void v_func2()
        {
            cout << "This is base_class's v_func2()" << endl;
        }
        virtual void v_func3()
        {
            cout << "This is base_class's v_func3()" << endl;
        }
    };
    class dev_class : public base_class
    {
    public:
        virtual void v_func4()
        {
            cout << "This is dev_class's v_func4()" << endl;
        }
        virtual void v_func5()
        {
            cout << "This is dev_class's v_func5()" << endl;
        }
    };
    
    int main()
    {
        // 查看 dev_class 的虚函数表
        dev_class dc;
        cout << "dev_class 的虚函数表首地址为:" << (int*)&dc << endl;
        cout << "dev_class 的 第一个函数首地址:" << (int*)*(int*)&dc+0 << endl;
        cout << "dev_class 的 第二个函数首地址:" << (int*)*(int*)&dc+1 << endl;
        cout << "dev_class 的 第三个函数首地址:" << (int*)*(int*)&dc+2 << endl;
        cout << "dev_class 的 第四个函数首地址:" << (int*)*(int*)&dc+3 << endl;
        cout << "dev_class 的 第五个函数首地址:" << (int*)*(int*)&dc+4 << endl;
        cout << "dev_class 的虚函数表结束标志: " << *((int*)*(int*)&dc+5) << endl;
        // 通过函数指针调用函数,验证正确性
        typedef void(*func_pointer)(void);
        func_pointer fp = NULL;
        for (int i=0; i<5; i++) {
            fp = (func_pointer)*((int*)*(int*)&dc+i);
            fp();
        }
        return 0;
    }
    复制代码

     输出结果:

    复制代码
    dev_class 的虚函数表首地址为:0x22ff0c
    dev_class 的 第一个函数首地址:0x472d10
    dev_class 的 第二个函数首地址:0x472d14
    dev_class 的 第三个函数首地址:0x472d18
    dev_class 的 第四个函数首地址:0x472d1c
    dev_class 的 第五个函数首地址:0x472d20
    dev_class 的虚函数表结束标志: 0
    This is base_class's v_func1()
    This is base_class's v_func2()
    This is base_class's v_func3()
    This is dev_class's v_func4()
    This is dev_class's v_func5()
    复制代码

    通过上面的例子的尝试和输出结果,我们可以得出下面的布局图示:


    可以看出,v-table中虚函数是顺序存放的,先基类后派生类。

    3.2子类有重写父类的虚函数

    复制代码
    include <iostream>
    using namespace std;
    
    class base_class
    {
    public:
        virtual void v_func1()
        {
            cout << "This is base_class's v_func1()" << endl;
        }
        virtual void v_func2()
        {
            cout << "This is base_class's v_func2()" << endl;
        }
        virtual void v_func3()
        {
            cout << "This is base_class's v_func3()" << endl;
        }
    };
    class dev_class : public base_class
    {
    public:
        virtual void v_func1()
        {
            cout << "This is dev_class's v_func1()" << endl;
        }
        virtual void v_func2()
        {
            cout << "This is dev_class's v_func2()" << endl;
        }
        virtual void v_func4()
        {
            cout << "This is dev_class's v_func4()" << endl;
        }
        virtual void v_func5()
        {
            cout << "This is dev_class's v_func5()" << endl;
        }
    };
    
    int main()
    {
        // 查看 dev_class 的虚函数表
        dev_class dc;
        cout << "dev_class 的虚函数表首地址为:" << (int*)&dc << endl;
        cout << "dev_class 的 第一个函数首地址:" << (int*)*(int*)&dc+0 << endl;
        cout << "dev_class 的 第二个函数首地址:" << (int*)*(int*)&dc+1 << endl;
        cout << "dev_class 的 第三个函数首地址:" << (int*)*(int*)&dc+2 << endl;
        cout << "dev_class 的 第四个函数首地址:" << (int*)*(int*)&dc+3 << endl;
        cout << "dev_class 的 第五个函数首地址:" << (int*)*(int*)&dc+4 << endl;
        cout << "dev_class 的虚函数表结束标志: " << *((int*)*(int*)&dc+5) << endl;
        // 通过函数指针调用函数,验证正确性
        typedef void(*func_pointer)(void);
        func_pointer fp = NULL;
        for (int i=0; i<5; i++) {
            fp = (func_pointer)*((int*)*(int*)&dc+i);
            fp();
        }
        return 0;
    }
    复制代码

    输出结果:

    复制代码
    dev_class 的虚函数表首地址为:0x22ff0c
    dev_class 的 第一个函数首地址:0x472d50
    dev_class 的 第二个函数首地址:0x472d54
    dev_class 的 第三个函数首地址:0x472d58
    dev_class 的 第四个函数首地址:0x472d5c
    dev_class 的 第五个函数首地址:0x472d60
    dev_class 的虚函数表结束标志: 0
    This is dev_class's v_func1()
    This is dev_class's v_func2()
    This is base_class's v_func3()
    This is dev_class's v_func4()
    This is dev_class's v_func5()
    复制代码

     

    通过上面的例子的尝试和输出结果,我们可以得出下面的布局图示:

    可以看出当派生类中 dev_class 中重写了父类 base_class 的前两个虚函数(v_func1,v_func2)之后,使用派生类的虚函数指针代替了父类的虚函数。未重写的父类虚函数位置没有发生变化。

    不知道看到这里,你心里有没有一个小问题?至少我是有的。看下面的代码:

    virtual void v_func1()
    {
        base_class::v_func1();
        cout << "This is dev_class's v_func1()" << endl;
    }

    既然派生类的虚函数表中用 dev_class::v_func1 指针代替了 base_class::v_func1,假如我显示的调用 base_class::v_func1,会不会有错呢?答案是没错的,可以正确的调用!不是覆盖了吗?dev_class 已经不知道 base_class::v_func1 的指针了,怎么调用的呢?
    如果你想知道原因,请关注这两个帖子:

    http://stackoverflow.com/questions/11426970/why-can-a-derived-class-virtual-function-call-a-base-class-virtual-fuction-how

    http://topic.csdn.net/u/20120711/14/fa9cfba2-8814-4119-8290-99e6af2c21f4.html?seed=742904136&r=79093804#r_79093804

    四、多重继承下的虚函数表


    4.1子类没有重写父类的虚函数

    复制代码
    #include <iostream>
    using namespace std;
    
    class base_class1
    {
    public:
        virtual void bc1_func1()
        {
            cout << "This is bc1_func1's v_func1()" << endl;
        }
    };
    
    class base_class2
    {
    public:
        virtual void bc2_func1()
        {
            cout << "This is bc2_func1's v_func1()" << endl;
        }
    };
    
    class dev_class : public base_class1, public base_class2
    {
    public:
        virtual void dc_func1()
        {
            cout << "This is dc_func1's dc_func1()" << endl;
        }
    };
    
    int main()
    {
        dev_class dc;
        cout << "dc 的虚函数表 bc1_vt 地址:" << (int*)&dc << endl;
        cout << "dc 的虚函数表 bc1_vt 第一个虚函数地址:" << (int*)*(int*)&dc+0 << endl;
        cout << "dc 的虚函数表 bc1_vt 第二个虚函数地址:" << (int*)*(int*)&dc+1 << endl;
        cout << "dc 的虚函数表 bc1_vt 结束标志:" << *((int*)*(int*)&dc+2) << endl;
        cout << "dc 的虚函数表 bc2_vt 地址:" << (int*)&dc+1 << endl;
        cout << "dc 的虚函数表 bc2_vt 第一个虚函数首地址::" << (int*)*((int*)&dc+1)+0 << endl;
        cout << "dc 的虚函数表 bc2_vt 结束标志:" << *((int*)*((int*)&dc+1)+1) << endl;
        // 通过函数指针调用函数,验证正确性
        typedef void(*func_pointer)(void);
        func_pointer fp = NULL;
        // bc1_vt
        fp = (func_pointer)*((int*)*(int*)&dc+0);
        fp();
        fp = (func_pointer)*((int*)*(int*)&dc+1);
        fp();
        // bc2_vt
        fp = (func_pointer)*(((int*)*((int*)&dc+1)+0));
        fp();
        return 0;
    }
    复制代码

    输出结果:

    复制代码
    dc 的虚函数表 bc1_vt 地址:0x22ff08
    dc 的虚函数表 bc1_vt 第一个虚函数地址:0x472d38
    dc 的虚函数表 bc1_vt 第二个虚函数地址:0x472d3c
    dc 的虚函数表 bc1_vt 结束标志:-4
    dc 的虚函数表 bc2_vt 地址:0x22ff0c
    dc 的虚函数表 bc2_vt 第一个虚函数首地址::0x472d48
    dc 的虚函数表 bc2_vt 结束标志:0
    This is bc1_func1's v_func1()
    This is dc_func1's dc_func1()
    This is bc2_func1's v_func1()
    复制代码

    通过上面的例子的尝试和输出结果,我们可以得出下面的布局图示:


    可以看出:多重继承的情况,会为每一个基类建一个虚函数表。派生类的虚函数放到第一个虚函数表的后面。

    陈皓在他的文章中有这么一句话:“这个结束标志(虚函数表)的值在不同的编译器下是不同的。在WinXP+VS2003下,这个值是NULL。而在Ubuntu 7.10 + Linux 2.6.22 + GCC 4.1.3下,这个值是如果1,表示还有下一个虚函数表,如果值是0,表示是最后一个虚函数表。”。那么,我在 Windows 7 + Code::blocks 10.05 下尝试,这个值是如果是 -4,表示还有下一个虚函数表,如果是0,表示是最后一个虚函数表。
    我在 Windows 7 + vs2010 下尝试,两个值都是 0 。

    4.2子类重写了父类的虚函数

    复制代码
    #include <iostream>
    using namespace std;
    
    class base_class1
    {
    public:
        virtual void bc1_func1()
        {
            cout << "This is base_class1's bc1_func1()" << endl;
        }
        virtual void bc1_func2()
        {
            cout << "This is base_class1's bc1_func2()" << endl;
        }
    };
    
    class base_class2
    {
    public:
        virtual void bc2_func1()
        {
            cout << "This is base_class2's bc2_func1()" << endl;
        }
        virtual void bc2_func2()
        {
            cout << "This is base_class2's bc2_func2()" << endl;
        }
    };
    
    class dev_class : public base_class1, public base_class2
    {
    public:
        virtual void bc1_func1()
        {
            cout << "This is dev_class's bc1_func1()" << endl;
        }
        virtual void bc2_func1()
        {
            cout << "This is dev_class's bc2_func1()" << endl;
        }
        virtual void dc_func1()
        {
            cout << "This is dev_class's dc_func1()" << endl;
        }
    };
    
    int main()
    {
        dev_class dc;
        cout << "dc 的虚函数表 bc1_vt 地址:" << (int*)&dc << endl;
        cout << "dc 的虚函数表 bc1_vt 第一个虚函数地址:" << (int*)*(int*)&dc+0 << endl;
        cout << "dc 的虚函数表 bc1_vt 第二个虚函数地址:" << (int*)*(int*)&dc+1 << endl;
        cout << "dc 的虚函数表 bc1_vt 第三个虚函数地址:" << (int*)*(int*)&dc+2 << endl;
        cout << "dc 的虚函数表 bc1_vt 第四个虚函数地址:" << (int*)*(int*)&dc+3 << endl;
        cout << "dc 的虚函数表 bc1_vt 结束标志:" << *((int*)*(int*)&dc+4) << endl;
        cout << "dc 的虚函数表 bc2_vt 地址:" << (int*)&dc+1 << endl;
        cout << "dc 的虚函数表 bc2_vt 第一个虚函数首地址::" << (int*)*((int*)&dc+1)+0 << endl;
        cout << "dc 的虚函数表 bc2_vt 第二个虚函数首地址::" << (int*)*((int*)&dc+1)+1 << endl;
        cout << "dc 的虚函数表 bc2_vt 结束标志:" << *((int*)*((int*)&dc+1)+2) << endl;
        // 通过函数指针调用函数,验证正确性
        typedef void(*func_pointer)(void);
        func_pointer fp = NULL;
        // bc1_vt
        fp = (func_pointer)*((int*)*(int*)&dc+0);
        fp();
        fp = (func_pointer)*((int*)*(int*)&dc+1);
        fp();
        fp = (func_pointer)*((int*)*(int*)&dc+2);
        fp();
        fp = (func_pointer)*((int*)*(int*)&dc+3);
        fp();
        // bc2_vt
        fp = (func_pointer)*(((int*)*((int*)&dc+1)+0));
        fp();
        fp = (func_pointer)*(((int*)*((int*)&dc+1)+1));
        fp();
        return 0;
    }
    复制代码

     

    输出结果:

    复制代码
    dc 的虚函数表 bc1_vt 地址:0x22ff08
    dc 的虚函数表 bc1_vt 第一个虚函数地址:0x472e28
    dc 的虚函数表 bc1_vt 第二个虚函数地址:0x472e2c
    dc 的虚函数表 bc1_vt 第三个虚函数地址:0x472e30
    dc 的虚函数表 bc1_vt 第四个虚函数地址:0x472e34
    dc 的虚函数表 bc1_vt 结束标志:-4
    dc 的虚函数表 bc2_vt 地址:0x22ff0c
    dc 的虚函数表 bc2_vt 第一个虚函数首地址::0x472e40
    dc 的虚函数表 bc2_vt 第一个虚函数首地址::0x472e44
    dc 的虚函数表 bc2_vt 结束标志:0
    This is dev_class's bc1_func1()
    This is base_class1's bc1_func2()
    This is dev_class's bc2_func1()
    This is dev_class's dc_func1()
    This is dev_class's bc2_func1()
    This is base_class2's bc2_func2()
    复制代码

    通过上面的例子的尝试和输出结果,我们可以得出下面的布局图示:


    是不是感觉很乱?其实一点都不乱!就是两个单继承而已。把多余的部分(派生类的虚函数)增加到第一个虚函数表的最后,CB(Code::Blocks)是这样实现的。我试了一下,vs2010不是这样实现的,读者可以自己尝试一下。本文只针对 CB 来探讨。

    有人觉得多重继承不好理解。我想如果你明白了它的虚函数表是怎么样的,也就没什么不好理解了吧。
    也许还有人会说,不同的编译器实现方式是不一样的,我去研究某一种编译器的实现有什么意义呢?我个人理解是这样的:1.实现方式是不一样的,但是它们的实现结果是一样的(多态)。2.无论你了解虚函数表或者不了解虚函数表,我相信你都很少会用到它。但是当你了解了它的实现机制之后,你再去看多态,再去写虚函数的时候[作为你一个coder],相信你的感觉是不一样的。你会感觉很透彻,不会有丝毫的犹豫。3.学习编译器这种处理问题的方式(思想),这才是最重要的。[好像扯远了,^-^]。
    如果你了解了虚函数表之后,可以通过虚函数表直接访问类的方法,这种访问是不受成员的访问权限限制的(private,protected)。这样做是很危险的,但是确实是可以这样做的。这也是C++为什么很危险的语言的一个原因……

    看完之后,你不是产生了许多其他的问题呢?至少我有了几个问题[我这人问题特别多。^-^]比如:
    1.访问权限是怎么实现的?编译器怎么知道哪些函数是public,哪些是protected?
    2.虚函数调用是通过虚函数表实现的,那么非虚成员函数存放在哪里?是怎么实现的呢?
    3.类的成员存放在什么位置?怎么继承的呢?[这是对象布局问题,=.=]
    你知道的越多,你感觉你知道的越少。推荐大家一本书吧,《深度探索C++对象模型》(英文名字是《Inside to C++ Object Model》),看完你会明白很多。


     

    感谢阅读,下面列出参考资料[顺便给大家推荐一下陈皓的博客吧:http://coolshell.cn/,经常去逛逛,会学到很多,至少我是这样觉得的。^-^]:
    1.http://blog.csdn.net/haoel/article/details/1948051/
    2.http://baike.baidu.com/view/3750123.htm 
    3.http://www.cnblogs.com/wirelesser/archive/2008/03/09/1097463.html

     

    2012.07.20 update:
    1.本文只针对 Windows 7 Code::blocks 10.05 进行测试和讲解;
    2.不同的编译器实现方式可能不同,比如 VS2010 和 CB 10.05 就有些不同,感兴趣的朋友可自行测试。
    感谢 Adoo 的提醒,文章中以上两点有所提示,但是不是很明显,确实应该很明确的说清楚这个问题。

    展开全文
  • C++虚函数虚函数表解析 原文链接:http://www.keepsimply.org/2012/07/11/cpp-vtable/ 作者:独酌逸醉 时间:2012.07.11 声明:  本文内容由自互联网资源(见参考资料)、个人的一些 C++ 学习感悟...

    C++虚函数及虚函数表解析

    原文链接:http://www.keepsimply.org/2012/07/11/cpp-vtable/

    作者:独酌逸醉
    时间:2012.07.11

    声明:
      本文内容由自互联网资源(见参考资料)、个人的一些 C++ 学习感悟、个人实践整理而成。文章仅以技术学习和交流为目的。如果您发现了文中的错误,或者您有的不同的见解,可以给我留言或者给我发邮件,我们共同探 讨。如果您觉得我的文章侵犯到您的权益,请联系我(chinajiezhang@gmail.com),以便我做相应的处理。最后,如需转载,可不必标明 出处。但一定要全文转载,保证参考链接的完整性,这是对别人写作的基本尊重。谢谢合作!

    写博缘由:
      1.对C++多态内部机制了解的渴望;
      2.眼过千遍,不如手过一遍;
      3.整理成文,帮助自己记忆;不求帮到他人,只求不会误导。


    一、背景知识(一些基本概念)

    虚函数(Virtual Function):在基类中声明为 virtual 并在一个或多个派生类中被重新定义的成员函数。
    纯虚函数(Pure Virtual Function):基类中没有实现体的虚函数称为纯虚函数(有纯虚函数的基类称为虚基类)。
    C++  “虚函数”的存在是为了实现面向对象中的“多态”,即父类类别的指针(或者引用)指向其子类的实例,然后通过父类的指针(或者引用)调用实际子类的成员函数。通过动态赋值,实现调用不同的子类的成员函数(动态绑定)。正是因为这种机制,把析构函数声明为“虚函数”可以防止在内存泄露。
    实例:

    复制代码
    #include <iostream>
    using namespace std;
    
    class base_class
    {
    public:
        base_class()
        {
        }
        virtual ~base_class()
        {
        }
    
        int normal_func()
        {
            cout << "This is  base_class's normal_func()" << endl;
            return 0;
        }
        virtual int virtual_fuc()
        {
            cout << "This is  base_class's virtual_fuc()" << endl;
            return 0;
        }
    
    };
    
    class drived_class1 : public base_class
    {
    public:
        drived_class1()
        {
        }
        virtual ~drived_class1()
        {
        }
    
        int normal_func()
        {
            cout << "This is  drived_class1's normal_func()" << endl;
            return 0;
        }
        virtual int virtual_fuc()
        {
            cout << "This is  drived_class1's virtual_fuc()" << endl;
            return 0;
        }
    };
    
    class drived_class2 : public base_class
    {
    public:
        drived_class2()
        {
        }
        virtual ~drived_class2()
        {
        }
    
        int normal_func()
        {
            cout << "This is  drived_class2's normal_func()" << endl;
            return 0;
        }
        virtual int virtual_fuc()
        {
            cout << "This is  drived_class2's virtual_fuc()" << endl;
            return 0;
        }
    };
    
    int main()
    {
        base_class * pbc = NULL;
        base_class bc;
        drived_class1 dc1;
        drived_class2 dc2;
    
        pbc = &bc;
        pbc->normal_func();
        pbc->virtual_fuc();
    
        pbc = &dc1;
        pbc->normal_func();
        pbc->virtual_fuc();
    
        pbc = &dc2;
        pbc->normal_func();
        pbc->virtual_fuc();
        return 0;
    }
    复制代码

    输出结果:

    复制代码
    This is  base_class's normal_func()
    This is  base_class's virtual_fuc()
    This is  base_class's normal_func()
    This is  drived_class1's virtual_fuc()
    This is  base_class's normal_func()
    This is  drived_class2's virtual_fuc()
    复制代码

     

    假如将 base_class 类中的 virtual_fuc() 写成下面这样(纯虚函数,虚基类):

    // 无实现体
    virtual int virtual_fuc() = 0;

    那么 virtual_fuc() 是一个纯虚函数,base_class 就是一个虚基类:不能实例化(就是不能用它来定义对象),只能声明指针或者引用。读者可以自行测试,这里不再给出实例。


    虚函数表(Virtual Table,V-Table):使用 V-Table 实现 C++ 的多态。在这个表中,主要是一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其真实反应实际的函数。这样,在有虚函数的类的实例中分配了指向这个表的指针的内存,所以,当用父类的指针来操作一个子类的时候,这张虚函数表就显得尤为重要了,它就像一个地图一样,指明了实际所应该调用的函数。
    编译器应该保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下)。 这意味着可以通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。

    二、无继承时的虚函数表

    复制代码
    #include <iostream>
    using namespace std;
    
    class base_class
    {
    public:
        virtual void v_func1()
        {
            cout << "This is base_class's v_func1()" << endl;
        }
        virtual void v_func2()
        {
            cout << "This is base_class's v_func2()" << endl;
        }
        virtual void v_func3()
        {
            cout << "This is base_class's v_func3()" << endl;
        }
    };
    
    int main()
    {
        // 查看 base_class 的虚函数表
        base_class bc;
        cout << "base_class 的虚函数表首地址为:" << (int*)&bc << endl; // 虚函数表地址存在对象的前四个字节
        cout << "base_class 的 第一个函数首地址:" << (int*)*(int*)&bc+0 << endl; // 指针运算看不懂?没关系,一会解释给你听
        cout << "base_class 的 第二个函数首地址:" << (int*)*(int*)&bc+1 << endl;
        cout << "base_class 的 第三个函数首地址:" << (int*)*(int*)&bc+2 << endl;
        cout << "base_class 的 结束标志: " << *((int*)*(int*)&bc+3) << endl;
        
        // 通过函数指针调用函数,验证正确性
        typedef void(*func_pointer)(void);
        func_pointer fp = NULL;
        fp = (func_pointer)*((int*)*(int*)&bc+0); // v_func1()
        fp();
        fp = (func_pointer)*((int*)*(int*)&bc+1); // v_func2()
        fp();
        fp = (func_pointer)*((int*)*(int*)&bc+2); // v_func3()
        fp();
        return 0;
    }
    复制代码

    输出结果:

    复制代码
    base_class 的虚函数表首地址为:0x22ff0c
    base_class 的 第一个函数首地址:0x472c98
    base_class 的 第二个函数首地址:0x472c9c
    base_class 的 第三个函数首地址:0x472ca0
    base_class 的虚函数表结束标志: 0
    This is base_class's v_func1()
    This is base_class's v_func2()
    This is base_class's v_func3()
    复制代码


    简单的解释一下代码中的指针转换:
    &bc:获得 bc 对象的地址
    (int*)&bc: 类型转换,获得虚函数表的首地址。这里使用 int* 的原因是函数指针的大小的 4byte,使用 int* 可以使得他们每次的偏移量保持一致(sizeof(int*) = 4,32-bit机器)。
    *(int*)&bc:解指针引用,获得虚函数表。
    (int*)*(int*)&bc+0:和上面相同的类型转换,获得虚函数表的第一个虚函数地址。
    (int*)*(int*)&bc+1:同上,获得第二个函数地址。
    (int*)*(int*)&bc+2:同上,获得第三个函数地址。
    *((int*)*(int*)&bc+3:获得虚函数表的结束标志,所以这里我解引用了。和我们使用链表的情况是一样的,虚函数表当然也需要一个结束标志。
    typedef void(*func_pointer)(void):定义一个函数指针,参数和返回值都是 void。
    *((int*)*(int*)&bc+0):找到第一个函数,注意这里需要解引用。
    对于指针的转换,我就解释这么多了。下面的文章,我不再做解释,相信大家可以举一反三。如果你觉得很费解的话,我不建议继续去看这篇文章了,建议你去补一补基础(《C和指针》是一本很好的选择哦!)。

    通过上面的例子的尝试和输出结果,我们可以得出下面的布局图示:

    三、单一继承下的虚函数表
    3.1子类没有父类的虚函数(陈皓文章中用了“覆盖”一词,我觉得太合理,但是我又找不到更合理的词语,所以就用一个句子代替了。^-^)

    复制代码
    #include <iostream>
    using namespace std;
    
    class base_class
    {
    public:
        virtual void v_func1()
        {
            cout << "This is base_class's v_func1()" << endl;
        }
        virtual void v_func2()
        {
            cout << "This is base_class's v_func2()" << endl;
        }
        virtual void v_func3()
        {
            cout << "This is base_class's v_func3()" << endl;
        }
    };
    class dev_class : public base_class
    {
    public:
        virtual void v_func4()
        {
            cout << "This is dev_class's v_func4()" << endl;
        }
        virtual void v_func5()
        {
            cout << "This is dev_class's v_func5()" << endl;
        }
    };
    
    int main()
    {
        // 查看 dev_class 的虚函数表
        dev_class dc;
        cout << "dev_class 的虚函数表首地址为:" << (int*)&dc << endl;
        cout << "dev_class 的 第一个函数首地址:" << (int*)*(int*)&dc+0 << endl;
        cout << "dev_class 的 第二个函数首地址:" << (int*)*(int*)&dc+1 << endl;
        cout << "dev_class 的 第三个函数首地址:" << (int*)*(int*)&dc+2 << endl;
        cout << "dev_class 的 第四个函数首地址:" << (int*)*(int*)&dc+3 << endl;
        cout << "dev_class 的 第五个函数首地址:" << (int*)*(int*)&dc+4 << endl;
        cout << "dev_class 的虚函数表结束标志: " << *((int*)*(int*)&dc+5) << endl;
        // 通过函数指针调用函数,验证正确性
        typedef void(*func_pointer)(void);
        func_pointer fp = NULL;
        for (int i=0; i<5; i++) {
            fp = (func_pointer)*((int*)*(int*)&dc+i);
            fp();
        }
        return 0;
    }
    复制代码

     输出结果:

    复制代码
    dev_class 的虚函数表首地址为:0x22ff0c
    dev_class 的 第一个函数首地址:0x472d10
    dev_class 的 第二个函数首地址:0x472d14
    dev_class 的 第三个函数首地址:0x472d18
    dev_class 的 第四个函数首地址:0x472d1c
    dev_class 的 第五个函数首地址:0x472d20
    dev_class 的虚函数表结束标志: 0
    This is base_class's v_func1()
    This is base_class's v_func2()
    This is base_class's v_func3()
    This is dev_class's v_func4()
    This is dev_class's v_func5()
    复制代码

    通过上面的例子的尝试和输出结果,我们可以得出下面的布局图示:


    可以看出,v-table中虚函数是顺序存放的,先基类后派生类。

    3.2子类有重写父类的虚函数

    复制代码
    include <iostream>
    using namespace std;
    
    class base_class
    {
    public:
        virtual void v_func1()
        {
            cout << "This is base_class's v_func1()" << endl;
        }
        virtual void v_func2()
        {
            cout << "This is base_class's v_func2()" << endl;
        }
        virtual void v_func3()
        {
            cout << "This is base_class's v_func3()" << endl;
        }
    };
    class dev_class : public base_class
    {
    public:
        virtual void v_func1()
        {
            cout << "This is dev_class's v_func1()" << endl;
        }
        virtual void v_func2()
        {
            cout << "This is dev_class's v_func2()" << endl;
        }
        virtual void v_func4()
        {
            cout << "This is dev_class's v_func4()" << endl;
        }
        virtual void v_func5()
        {
            cout << "This is dev_class's v_func5()" << endl;
        }
    };
    
    int main()
    {
        // 查看 dev_class 的虚函数表
        dev_class dc;
        cout << "dev_class 的虚函数表首地址为:" << (int*)&dc << endl;
        cout << "dev_class 的 第一个函数首地址:" << (int*)*(int*)&dc+0 << endl;
        cout << "dev_class 的 第二个函数首地址:" << (int*)*(int*)&dc+1 << endl;
        cout << "dev_class 的 第三个函数首地址:" << (int*)*(int*)&dc+2 << endl;
        cout << "dev_class 的 第四个函数首地址:" << (int*)*(int*)&dc+3 << endl;
        cout << "dev_class 的 第五个函数首地址:" << (int*)*(int*)&dc+4 << endl;
        cout << "dev_class 的虚函数表结束标志: " << *((int*)*(int*)&dc+5) << endl;
        // 通过函数指针调用函数,验证正确性
        typedef void(*func_pointer)(void);
        func_pointer fp = NULL;
        for (int i=0; i<5; i++) {
            fp = (func_pointer)*((int*)*(int*)&dc+i);
            fp();
        }
        return 0;
    }
    复制代码

    输出结果:

    复制代码
    dev_class 的虚函数表首地址为:0x22ff0c
    dev_class 的 第一个函数首地址:0x472d50
    dev_class 的 第二个函数首地址:0x472d54
    dev_class 的 第三个函数首地址:0x472d58
    dev_class 的 第四个函数首地址:0x472d5c
    dev_class 的 第五个函数首地址:0x472d60
    dev_class 的虚函数表结束标志: 0
    This is dev_class's v_func1()
    This is dev_class's v_func2()
    This is base_class's v_func3()
    This is dev_class's v_func4()
    This is dev_class's v_func5()
    复制代码

     

    通过上面的例子的尝试和输出结果,我们可以得出下面的布局图示:

    可以看出当派生类中 dev_class 中重写了父类 base_class 的前两个虚函数(v_func1,v_func2)之后,使用派生类的虚函数指针代替了父类的虚函数。未重写的父类虚函数位置没有发生变化。

    不知道看到这里,你心里有没有一个小问题?至少我是有的。看下面的代码:

    virtual void v_func1()
    {
        base_class::v_func1();
        cout << "This is dev_class's v_func1()" << endl;
    }

    既然派生类的虚函数表中用 dev_class::v_func1 指针代替了 base_class::v_func1,假如我显示的调用 base_class::v_func1,会不会有错呢?答案是没错的,可以正确的调用!不是覆盖了吗?dev_class 已经不知道 base_class::v_func1 的指针了,怎么调用的呢?
    如果你想知道原因,请关注这两个帖子:

    http://stackoverflow.com/questions/11426970/why-can-a-derived-class-virtual-function-call-a-base-class-virtual-fuction-how

    http://topic.csdn.net/u/20120711/14/fa9cfba2-8814-4119-8290-99e6af2c21f4.html?seed=742904136&r=79093804#r_79093804

    四、多重继承下的虚函数表


    4.1子类没有重写父类的虚函数

    复制代码
    #include <iostream>
    using namespace std;
    
    class base_class1
    {
    public:
        virtual void bc1_func1()
        {
            cout << "This is bc1_func1's v_func1()" << endl;
        }
    };
    
    class base_class2
    {
    public:
        virtual void bc2_func1()
        {
            cout << "This is bc2_func1's v_func1()" << endl;
        }
    };
    
    class dev_class : public base_class1, public base_class2
    {
    public:
        virtual void dc_func1()
        {
            cout << "This is dc_func1's dc_func1()" << endl;
        }
    };
    
    int main()
    {
        dev_class dc;
        cout << "dc 的虚函数表 bc1_vt 地址:" << (int*)&dc << endl;
        cout << "dc 的虚函数表 bc1_vt 第一个虚函数地址:" << (int*)*(int*)&dc+0 << endl;
        cout << "dc 的虚函数表 bc1_vt 第二个虚函数地址:" << (int*)*(int*)&dc+1 << endl;
        cout << "dc 的虚函数表 bc1_vt 结束标志:" << *((int*)*(int*)&dc+2) << endl;
        cout << "dc 的虚函数表 bc2_vt 地址:" << (int*)&dc+1 << endl;
        cout << "dc 的虚函数表 bc2_vt 第一个虚函数首地址::" << (int*)*((int*)&dc+1)+0 << endl;
        cout << "dc 的虚函数表 bc2_vt 结束标志:" << *((int*)*((int*)&dc+1)+1) << endl;
        // 通过函数指针调用函数,验证正确性
        typedef void(*func_pointer)(void);
        func_pointer fp = NULL;
        // bc1_vt
        fp = (func_pointer)*((int*)*(int*)&dc+0);
        fp();
        fp = (func_pointer)*((int*)*(int*)&dc+1);
        fp();
        // bc2_vt
        fp = (func_pointer)*(((int*)*((int*)&dc+1)+0));
        fp();
        return 0;
    }
    复制代码

    输出结果:

    复制代码
    dc 的虚函数表 bc1_vt 地址:0x22ff08
    dc 的虚函数表 bc1_vt 第一个虚函数地址:0x472d38
    dc 的虚函数表 bc1_vt 第二个虚函数地址:0x472d3c
    dc 的虚函数表 bc1_vt 结束标志:-4
    dc 的虚函数表 bc2_vt 地址:0x22ff0c
    dc 的虚函数表 bc2_vt 第一个虚函数首地址::0x472d48
    dc 的虚函数表 bc2_vt 结束标志:0
    This is bc1_func1's v_func1()
    This is dc_func1's dc_func1()
    This is bc2_func1's v_func1()
    复制代码

    通过上面的例子的尝试和输出结果,我们可以得出下面的布局图示:


    可以看出:多重继承的情况,会为每一个基类建一个虚函数表。派生类的虚函数放到第一个虚函数表的后面。

    陈皓在他的文章中有这么一句话:“这个结束标志(虚函数表)的值在不同的编译器下是不同的。在WinXP+VS2003下,这个值是NULL。而在Ubuntu 7.10 + Linux 2.6.22 + GCC 4.1.3下,这个值是如果1,表示还有下一个虚函数表,如果值是0,表示是最后一个虚函数表。”。那么,我在 Windows 7 + Code::blocks 10.05 下尝试,这个值是如果是 -4,表示还有下一个虚函数表,如果是0,表示是最后一个虚函数表。
    我在 Windows 7 + vs2010 下尝试,两个值都是 0 。

    4.2子类重写了父类的虚函数

    复制代码
    #include <iostream>
    using namespace std;
    
    class base_class1
    {
    public:
        virtual void bc1_func1()
        {
            cout << "This is base_class1's bc1_func1()" << endl;
        }
        virtual void bc1_func2()
        {
            cout << "This is base_class1's bc1_func2()" << endl;
        }
    };
    
    class base_class2
    {
    public:
        virtual void bc2_func1()
        {
            cout << "This is base_class2's bc2_func1()" << endl;
        }
        virtual void bc2_func2()
        {
            cout << "This is base_class2's bc2_func2()" << endl;
        }
    };
    
    class dev_class : public base_class1, public base_class2
    {
    public:
        virtual void bc1_func1()
        {
            cout << "This is dev_class's bc1_func1()" << endl;
        }
        virtual void bc2_func1()
        {
            cout << "This is dev_class's bc2_func1()" << endl;
        }
        virtual void dc_func1()
        {
            cout << "This is dev_class's dc_func1()" << endl;
        }
    };
    
    int main()
    {
        dev_class dc;
        cout << "dc 的虚函数表 bc1_vt 地址:" << (int*)&dc << endl;
        cout << "dc 的虚函数表 bc1_vt 第一个虚函数地址:" << (int*)*(int*)&dc+0 << endl;
        cout << "dc 的虚函数表 bc1_vt 第二个虚函数地址:" << (int*)*(int*)&dc+1 << endl;
        cout << "dc 的虚函数表 bc1_vt 第三个虚函数地址:" << (int*)*(int*)&dc+2 << endl;
        cout << "dc 的虚函数表 bc1_vt 第四个虚函数地址:" << (int*)*(int*)&dc+3 << endl;
        cout << "dc 的虚函数表 bc1_vt 结束标志:" << *((int*)*(int*)&dc+4) << endl;
        cout << "dc 的虚函数表 bc2_vt 地址:" << (int*)&dc+1 << endl;
        cout << "dc 的虚函数表 bc2_vt 第一个虚函数首地址::" << (int*)*((int*)&dc+1)+0 << endl;
        cout << "dc 的虚函数表 bc2_vt 第二个虚函数首地址::" << (int*)*((int*)&dc+1)+1 << endl;
        cout << "dc 的虚函数表 bc2_vt 结束标志:" << *((int*)*((int*)&dc+1)+2) << endl;
        // 通过函数指针调用函数,验证正确性
        typedef void(*func_pointer)(void);
        func_pointer fp = NULL;
        // bc1_vt
        fp = (func_pointer)*((int*)*(int*)&dc+0);
        fp();
        fp = (func_pointer)*((int*)*(int*)&dc+1);
        fp();
        fp = (func_pointer)*((int*)*(int*)&dc+2);
        fp();
        fp = (func_pointer)*((int*)*(int*)&dc+3);
        fp();
        // bc2_vt
        fp = (func_pointer)*(((int*)*((int*)&dc+1)+0));
        fp();
        fp = (func_pointer)*(((int*)*((int*)&dc+1)+1));
        fp();
        return 0;
    }
    复制代码

     

    输出结果:

    复制代码
    dc 的虚函数表 bc1_vt 地址:0x22ff08
    dc 的虚函数表 bc1_vt 第一个虚函数地址:0x472e28
    dc 的虚函数表 bc1_vt 第二个虚函数地址:0x472e2c
    dc 的虚函数表 bc1_vt 第三个虚函数地址:0x472e30
    dc 的虚函数表 bc1_vt 第四个虚函数地址:0x472e34
    dc 的虚函数表 bc1_vt 结束标志:-4
    dc 的虚函数表 bc2_vt 地址:0x22ff0c
    dc 的虚函数表 bc2_vt 第一个虚函数首地址::0x472e40
    dc 的虚函数表 bc2_vt 第一个虚函数首地址::0x472e44
    dc 的虚函数表 bc2_vt 结束标志:0
    This is dev_class's bc1_func1()
    This is base_class1's bc1_func2()
    This is dev_class's bc2_func1()
    This is dev_class's dc_func1()
    This is dev_class's bc2_func1()
    This is base_class2's bc2_func2()
    复制代码

    通过上面的例子的尝试和输出结果,我们可以得出下面的布局图示:


    是不是感觉很乱?其实一点都不乱!就是两个单继承而已。把多余的部分(派生类的虚函数)增加到第一个虚函数表的最后,CB(Code::Blocks)是这样实现的。我试了一下,vs2010不是这样实现的,读者可以自己尝试一下。本文只针对 CB 来探讨。

    有人觉得多重继承不好理解。我想如果你明白了它的虚函数表是怎么样的,也就没什么不好理解了吧。
    也许还有人会说,不同的编译器实现方式是不一样的,我去研究某一种编译器的实现有什么意义呢?我个人理解是这样的:1.实现方式是不一样的,但是它们的实现结果是一样的(多态)。2.无论你了解虚函数表或者不了解虚函数表,我相信你都很少会用到它。但是当你了解了它的实现机制之后,你再去看多态,再去写虚函数的时候[作为你一个coder],相信你的感觉是不一样的。你会感觉很透彻,不会有丝毫的犹豫。3.学习编译器这种处理问题的方式(思想),这才是最重要的。[好像扯远了,^-^]。
    如果你了解了虚函数表之后,可以通过虚函数表直接访问类的方法,这种访问是不受成员的访问权限限制的(private,protected)。这样做是很危险的,但是确实是可以这样做的。这也是C++为什么很危险的语言的一个原因……

    看完之后,你不是产生了许多其他的问题呢?至少我有了几个问题[我这人问题特别多。^-^]比如:
    1.访问权限是怎么实现的?编译器怎么知道哪些函数是public,哪些是protected?
    2.虚函数调用是通过虚函数表实现的,那么非虚成员函数存放在哪里?是怎么实现的呢?
    3.类的成员存放在什么位置?怎么继承的呢?[这是对象布局问题,=.=]
    你知道的越多,你感觉你知道的越少。推荐大家一本书吧,《深度探索C++对象模型》(英文名字是《Inside to C++ Object Model》),看完你会明白很多。


     

    感谢阅读,下面列出参考资料[顺便给大家推荐一下陈皓的博客吧:http://coolshell.cn/,经常去逛逛,会学到很多,至少我是这样觉得的。^-^]:
    1.http://blog.csdn.net/haoel/article/details/1948051/
    2.http://baike.baidu.com/view/3750123.htm 
    3.http://www.cnblogs.com/wirelesser/archive/2008/03/09/1097463.html

     

    2012.07.20 update:
    1.本文只针对 Windows 7 Code::blocks 10.05 进行测试和讲解;
    2.不同的编译器实现方式可能不同,比如 VS2010 和 CB 10.05 就有些不同,感兴趣的朋友可自行测试。
    感谢 Adoo 的提醒,文章中以上两点有所提示,但是不是很明显,确实应该很明确的说清楚这个问题。

    展开全文
  • 参考链接 https://www.cnblogs.com/always-chang/p/6107437.html 浅拷贝里面的char* data 的值赋给了另一个对象的char *data。 这样析构的时候会出现delete同一个地址两次,发生错误。 深拷贝把data指向的内容拷贝...
  • 不引用该类 创建该类实例 调用缺失函数 ...非虚函数 ...编译、链接通过 ...编译、链接通过 ...编译通过、链接 通不过 ...这样,为了尽早发现问题,可以把成员函数都声明为虚函数,只有创建该对象实例,就立即会报错。
  • 编译通过,链接时有以下错误: 对‘vtable for xxxx’未定义的引用 对‘typeinfo for xxxx’未定义的引用 ... 吾分析了半天,发现是父类的虚函数必须这样写: virtual void read() = 0; vi...
  • 1. illigal use of abstract class "ClassName::Function()" --- 一般都是虚函数重写有错误2. undefined symbol --- 缺少对应lib文件,无法link
  • 今天写代码遇到了一个问题,在链接期编译器报错"undefined reference to vtable...",这个错误一般是由于有虚函数生命但未被定义,因此在虚函数表中找不到reference。然而我报错的这个类中根本就没写虚函数啊,反复...
  • 纯虚函数

    千次阅读 2014-10-14 20:14:27
    纯虚函数也是可以有实现体的,只是由于不能建立基类的对象,因此不能直接调用,但是可以在子类中通过作用域解析操作符静态调用。...从而在链接阶段发生错误,所以,好的实现方案就是不要把析构函数设置为纯虚的
  • 注意:C++中的virtual函数在继承的类中需要实现,如有些虚构函数...参考:链接器linker需要将虚函数表vtable 放入某个object file,但是linker无法找到正确的object文件。这个错误常见于刚刚创建一系列有继承关系的clas
  • QT应用程序在link的时候提示undefined referenceto vtable for "xxx::xxx",第一次遇到这个错误的时候还以为是虚函数使用出的问题,当然如果虚函数使用不当,比如在某个实现类中只有声明没有定义,也会报类似的错误...
  • 问题描述:在开发过程中,动态加载了一个库,然后传入一个类变量的指针进去,通过该指针调用类的方法。编译没有问题,运行过程中报链接不到该方法的错误。...解决方案:传入的类需要是一个虚类,通过调用虚函数来实现。
  • HelloCpp工程中,如果为HelloWorld实现触屏事件,加入如下虚函数声明: virtual bool ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent); virtual void ccTouchMoved(CCTouch *pTouch, CCEvent *pEvent); ...
  • 这两天研究了一下DLL的import/export原理,看了一些资料,无意中发现网上有一篇文章存在错误,而这篇文章流传还甚广,恐怕也误了不少子弟,觉得有必要说一下:)随便用哪个搜索引擎来搜索“C++ 虚函数 ILT”,排在前面...
  • 昨天遇到这个问题,很是纳闷,真不知道怎么解决,上网搜了下,找到了以下办法:---------------------------------------链接器linker需要将虚函数表vtable 放入某个object file,但是linker无法找到正确的object...
  • [C++] Undefined reference to vtable

    千次阅读 2006-09-20 10:27:00
    链接器linker需要将虚函数表vtable 放入某个object file,但是linker无法找到正确的object文件。这个错误常见于刚刚创建一系列有继承关系的class的时候,这个时候很容易忘了给base class的virtual function加上函数...
  • 一面  大概80分钟  先自我介绍2-3分钟  ...区别 c程序编译错误和链接错误是怎么产生的 多态 虚函数 java和c++的多态有啥区别 KMP算法思想  编程题 判断一个字符串是否为合法的十进制点IPv4地址,比如192.

空空如也

空空如也

1 2 3 4
收藏数 67
精华内容 26
关键字:

虚函数链接错误