C++迟后联编和虚函数表

简介:

先看一个题目:

复制代码
class Base
{
public:
    virtual void Show(int x)
    {
        cout << "In Base class, int x = " << x << endl;
    }
};

class Derived : public Base
{
public:
    virtual void Show(float x)
    {
        cout << "In Derived, float x = " << x << endl;
    }
};

void test (Base &b)
{
    int i = 1;
    b.Show(i);
    
    float f = 2.0;
    b.Show(f);
}

int main(int argc, char *argv[])
{
    Base bc;

    Derived sc;
    test(bc);
    test(sc);
 
    return 0;
}

输出结果为:D

A、In Base class, int x = 1;
   In Base class, int x = 2;
   In Derived, int x = 1;
   In Derived, float x = 2;
B、In Base class, int x = 1;
   In Base class, int x = 2;
   In Derived, float x = 1;
   In Derived, float x = 2;
C、In Base class, int x = 1;
   In Base class, int x = 2;
   In Base, int x = 1;
   In Base, float x = 2;
D、In Base class, int x = 1;
   In Base class, int x = 2;
   In Base class, int x = 1;
   In Base class, int x = 2;
复制代码

理由:如果虚函数在基类与子类中出现的仅仅是名字的相同,而参数类型不同,或者返回类型不同,即使写上了virtual关键字,也不进行迟后联编。

stackoverflow上,可以看到解释,http://stackoverflow.com/questions/27227189/override-virtual-function-with-different-parameters-in-c

C++里有两种编译类型:
1) 先期联编或静态联编:在编译时就能进行函数联编称为先期联编或静态联编。 
2) 迟后联编或动态联编:在运行时才能进行的联编称为迟后联编或动态联编。

virtual关键字的作用就是提示编译器进行迟后联编,告诉链接过程:“我是个虚的,先不要连接我,等运行时再说”。 具体原理:当编译器遇到virtual后,会为所在的类构造一个表和一个指针,那个表叫做vtbl,每个类都有自己的vtbl,vtbl的作用就是保存自己类中虚函数的地址,我们可以把vtbl形象地看成一个数组,这个数组的每个元素存放的就是虚函数的地址,指针叫做vptr,指向那个表。而这个指针保存在相应的对象当中,也就是说只有创建了对象以后才能找到相应虚函数的地址。 

对于下面这种常见代码(假如Base是Derive的父类):

Base *p=new Derive();
p->virtual_fun();

在程序运行时,根据对象的类型去初始化vptr,从而让vptr正确的指向所属类的虚表。上述程序中,由于p实际指向的对象类型是Derive,因此vptr指向的Derive类的vtable,当调用p->virtual_fun()时,根据虚表中的函数地址找到的就是Derive类的virtual_func()函数。

 

假设我们有这样的一个类:

复制代码
class Base {

     public:

            virtual void f() { cout << "Base::f" << endl; }

            virtual void g() { cout << "Base::g" << endl; }

            virtual void h() { cout << "Base::h" << endl; }

};
复制代码

对应的虚函数表:

假设有如下所示的一个继承关系:

对于实例:Derive d; 的虚函数表如下:

如果是多继承:

对于实例:Derive d; 的虚函数表如下:

而C++标准规定:为确保运行时的多态定义的基类与派生类的虚函数不仅函数名要相同,其返回值及参数都必须相同,否则即使加上了virtual,系统也不进行迟后联编。

因此,对于最初的题目,Base的虚函数表里仅仅有一个Show方法,由于Derived子类重载了Show函数,那么Derived的虚函数表里实际上有两个Show方法,

因此,从Base角度去调用Show方法也只能是调用Base自己的方法了。

 

虚函数表的例子参考了:http://blog.csdn.net/haoel/article/details/1948051


















本文转自张昺华-sky博客园博客,原文链接:http://www.cnblogs.com/bonelee/p/5824490.html,如需转载请自行联系原作者

相关文章
|
30天前
|
存储 安全 编译器
【C/C++ 多态核心 20240115更新】C++虚函数表:让多态成为可能的关键
【C/C++ 多态核心 20240115更新】C++虚函数表:让多态成为可能的关键
31 0
|
3月前
|
存储 NoSQL 编译器
『 C++类与对象 』多态之单继承与多继承的虚函数表
『 C++类与对象 』多态之单继承与多继承的虚函数表
|
3月前
|
C++
C++多态之虚函数表详解及代码示例
C++多态之虚函数表详解及代码示例
|
5月前
|
存储 Cloud Native 编译器
C++ 虚函数表和虚函数表指针的创建时机
C++ 虚函数表和虚函数表指针的创建时机
|
9月前
|
存储 设计模式 编译器
【C++的多态】多态的概念|| 多态的定义及实现||多态的原理||多态的构成条件||虚函数表||C++11 override 和 final ||重载、覆盖(重写)、隐藏(重定义)的对比||常见面试题
本篇将讲述多态的概念、多态的定义及实现、抽象类、多态的原理、单继承和多继承关系中的虚函数表以及继承和多态常见的面试问题 等。
81 0
|
10月前
|
存储 C++
C++ 虚函数,虚函数表
C++ 虚函数,虚函数表
|
存储 编译器 C++
【C++要笑着学】虚函数表(VBTL) | 观察虚表指针 | 运行时决议与编译时决议 | 动态绑定与静态绑定 | 静态多态与动态多态 | 单继承与多继承关系的虚表(二)
虚表是编译器的实现,而非C++的语言标准。上一章我们学习了多态的概念,本章我们深入探讨一下多态的原理。文章开头先说虚表指针,观察编译器的查表行为。首次观察我们先从监视窗口观察美化后的虚表 _vfptr,再透过内存窗口观察真实的 _vfptr。我们还会探讨为什么对象也能切片却不能实现多态的问题。对于虚表到底存在哪?我们会带着大家通过一些打印虚表的方式进行比对!铺垫完虚表的知识后,会讲解运行时决议与编译时决议,穿插动静态的知识点。文章的最后我们会探讨单继承与多继承的虚表,多继承中的虚表神奇的切片指针偏移问题,这块难度较大,后续我们会考虑专门讲解一下,顺带着把钻石虚拟继承给讲了
259 1
【C++要笑着学】虚函数表(VBTL) | 观察虚表指针 | 运行时决议与编译时决议 | 动态绑定与静态绑定 | 静态多态与动态多态 | 单继承与多继承关系的虚表(二)
|
存储 编译器 C++
【C++要笑着学】虚函数表(VBTL) | 观察虚表指针 | 运行时决议与编译时决议 | 动态绑定与静态绑定 | 静态多态与动态多态 | 单继承与多继承关系的虚表(一)
虚表是编译器的实现,而非C++的语言标准。上一章我们学习了多态的概念,本章我们深入探讨一下多态的原理。文章开头先说虚表指针,观察编译器的查表行为。首次观察我们先从监视窗口观察美化后的虚表 _vfptr,再透过内存窗口观察真实的 _vfptr。我们还会探讨为什么对象也能切片却不能实现多态的问题。对于虚表到底存在哪?我们会带着大家通过一些打印虚表的方式进行比对!铺垫完虚表的知识后,会讲解运行时决议与编译时决议,穿插动静态的知识点。文章的最后我们会探讨单继承与多继承的虚表,多继承中的虚表神奇的切片指针偏移问题,这块难度较大,后续我们会考虑专门讲解一下,顺带着把钻石虚拟继承给讲了
416 0
【C++要笑着学】虚函数表(VBTL) | 观察虚表指针 | 运行时决议与编译时决议 | 动态绑定与静态绑定 | 静态多态与动态多态 | 单继承与多继承关系的虚表(一)
|
存储 编译器 C语言
C++多态性原理详解(静态多态、动态多态、虚函数、虚函数表)
C++多态性原理详解(静态多态、动态多态、虚函数、虚函数表)
355 0
C++多态性原理详解(静态多态、动态多态、虚函数、虚函数表)