C/C++

推荐列表 站点导航

当前位置:首页 > 脚本编程 > C/C++ >

C++_C++ COM编程之接口背后的虚函数表,前言学习C++的人,肯定都知

来源:网络整理  作者:  发布时间:2020-12-20 13:34
C++ COM编程之接口背后的虚函数表,前言学习C++的人,肯定都知道多态机制;多态就是用父类型别的指针指向其子类的实...

好了,经过对上面代码的改造,就在A类的Print函数前面加入关键字virtual,具体代码如下:

复制代码 代码如下:

[6]     0x00000000     void *
class CIF

现在我在CIF2类中,重写CIF1类的IF1函数,它们的关系如下:

[0]     0x012014e2 {InterfaceDemo2.exe![thunk]:CIF3::IF1`adjustor{4}' (void)}     void *
public:


private:

多态

此时,代码的运行结果为:I am B.这个时候就表现出来了多态行为。好了,多了我也不说了,就通过这个简单的例子,你就能体会到多态的概念了。从下面才开始今天的主题。


在子类中重写了父类的虚函数,那它的虚函数表又是什么样子呢?

[3]     0x01151249 {InterfaceDemo2.exe!CIF2::IF4(void)}     void *
        cout<<"I am A."<<endl;

复制代码 代码如下:

复制代码 代码如下:

        cout<<"I am B."<<endl;
[5]     0x01151267 {InterfaceDemo2.exe!CIF2::IF6(void)}     void *
int main()

    void Print()
    {
class B : public A

        cout<<"I am B."<<endl;

虚函数表CIF1,如下:

[4]     0x012014d8 {InterfaceDemo2.exe!CIF3::IF5(void)}     void *

{

这个类的变量还没有被初始化时,就像上图那样,变量的值都是随机值,而指向虚拟函数表的指针__vfptr中对应的虚函数地址也是错误的地址;只有等我们真正的完成了这个变量的声明和初始化时,这些值才能被正确的初始化,如下图:

这样的一个类,当你去定义这个类的实例时,编译器会给这个类分配一个成员变量,该变量指向这个虚函数表,这个虚函数表中的每一项都会记录对应的虚函数的地址;如下图:

多态机制的关键就是在于虚函数表,也就是vtbl。当我们定义一个类,类中包含虚函数时,其实也就定义了一张虚函数表,没有虚函数的类是不包含虚函数表的,只有该类被实例化时,才会将这个表分配到这个实例的内存中;在这张虚函数表中,存放了每个虚函数的地址;它就像一个地图一样,指明了实际所应该调用的函数。比如我定义一个类,如下:

[4]     0x00b61433 {InterfaceDemo2.exe!CIF2::IF5(void)}     void *
[0]     0x00b61311 {InterfaceDemo2.exe!CIF2::IF1(void)}     void *
{


对于简单的,没有实现多态的多继承,比如,有下面的一个多继承关系:

总结了这么多关于虚函数表的内容,感觉很扯,和接口没有多大的关系;但是,这一切都是COM的基础,COM的背后,就是接口,而接口的背后,就是我这里总结的,说白了,完全了解了这里,对于理解COM的接口是有非常大的用处的。希望我的总结对大家有用。

}

     virtual void IF3() { cout<<"I'm IF3"<<endl; }

[6]     0x00000000     void *

你发现了什么?虚函数表中的第一项是CIF2::IF1,而不是CIF1::IF1,这说明了当在子类中重写父类的虚函数时,新的函数的地址覆盖了父类的虚函数地址,这样就能在多态时能正确的找到需要被调用的函数;而没有被覆盖的函数还是那样的顺序在虚函数表中存储着。

实现多态的单继承

    virtual void Print()
     virtual void IF1() { cout<<"I'm IF1"<<endl; }
[1]     0x012014ce {InterfaceDemo2.exe!CIF2::IF2(void)}     void *

可以发现,虚函数按照其声明顺序存放在表中,父类的虚函数在子类的虚函数前面。

[2]     0x01151343 {InterfaceDemo2.exe!CIF1::IF3(void)}     void *
[1]     0x00b612c6 {InterfaceDemo2.exe!CIF1::IF2(void)}     void *

在子类中没有重写任何父类的虚函数,那么它的虚函数表应该是什么样子呢?


};
    A *pAObj = new B();
    }


[2]     0x00b61343 {InterfaceDemo2.exe!CIF1::IF3(void)}     void *

没有实现多态的多继承

[3]     0x00b61249 {InterfaceDemo2.exe!CIF2::IF4(void)}     void *

    }

实现多态的多继承

复制代码 代码如下:

[2]     0x0120134d {InterfaceDemo2.exe!CIF1::IF3(void)}     void *

复制代码 代码如下:

 
[2]     0x012014d3 {InterfaceDemo2.exe!CIF2::IF3(void)}     void *
public:

[5]     0x00000000     void *

复制代码 代码如下:

};

比如有如下的继承关系:

};
    void Print()
[0]     0x012013cf {InterfaceDemo2.exe!CIF3::IF1(void)}     void *
[3]     0x01201456 {InterfaceDemo2.exe!CIF3::IF4(void)}     void *
[3]     0x00000000     void *
[1]     0x001e1447 {InterfaceDemo2.exe!CIF2::IF8(void)}     void *
};

在这个继承关系中,CIF2作为CIF1的子类,但是CIF2没有重写CIF1类的任何虚函数;定义CIF2 if2Obj;实例,在派生类的实例中,它的虚函数表应该是像下面这样的:
 

复制代码 代码如下:

using namespace std;
}
    {
int main()
[0]     0x011513c5 {InterfaceDemo2.exe!CIF1::IF1(void)}     void *
[0]     0x001e1258 {InterfaceDemo2.exe!CIF2::IF7(void)}     void *

什么是多态?上面也说了,多态就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。现在通过代码,让大家切身的体会一下多态:
[4]     0x001e1041 {InterfaceDemo2.exe!CIF3::IF5(void)}     void *
[3]     0x00000000     void *

上面说的是没有发生重写的情况,现在来说说发生重写的情况;比如,现在有以下情况:

[2]     0x001e127b {InterfaceDemo2.exe!CIF2::IF9(void)}     void *

上面代码的运行结果是:I am A.这不是多态的行为。

{
#include <iostream>
class B : public A

虚函数表CIF1,如下:

我们都知道,虚函数是用来支持C++中的多态的,而单独的一个类,有了虚函数,而没有任何继承关系,也就是说没有子类去覆盖父类的虚函数,这样是毫无意义的。所以下面就要从各个方面进行详细的说明虚函数表。

    }


 
     CIF(int i, int f) : m_iVar(i), m_fVar(f){}
[2]     0x001e1357 {InterfaceDemo2.exe!CIF1::IF3(void)}     void *

从上图中就可以看到,初始化完成以后,指向虚函数表的__vfptr指针中的元素都被赋予了正确的虚函数值,分别指向了在类中定义的三个虚函数。也看到了,__vfptr指针定义的位置也比m_iVar和m_fVar变量的位置靠前;在C++编译器中,它保证虚函数表的指针存在于对象实例中最前面的位置,这主要是为了在多层继承或是多重继承的情况下,能以高性能取到这张虚函数表,然后进行遍历,查找对应的虚函数指针,进行对应的调用。

虚函数表

    A *pAObj = new B();

从上面的虚函数表,我们可以分析出来,每个父类都有自己的虚函数表,子类的虚函数被放到了第一个父类的表中。第一个父类是按照声明顺序来判断的。

{


     int m_iVar;
 

    pAObj->Print();

虚函数表CIF2,如下:

[3]     0x001e10c8 {InterfaceDemo2.exe!CIF3::IF4(void)}     void *
    pAObj->Print();

总结

[1]     0x001e12df {InterfaceDemo2.exe!CIF1::IF2(void)}     void *
    void Print()

复制代码 代码如下:

    {
    {
{
[1]     0x012012d5 {InterfaceDemo2.exe!CIF1::IF2(void)}     void *

从上面的虚函数表中,我们可以看到虚函数表中的CIF1::IF1(void)全都被替换成了CIF3::IF1(void),那么我们就可以以任意的父类指针来调用IF1(void),实际上调用的是CIF3::IF1(void),这就实现了所谓的多态。

{
     CIF(){}

在上图中,CIF2继承了CIF1,并且在CIF2类中重写了CIF1的虚函数IF1,那我们现在看看虚函数表是什么样子的?

虚函数表CIF2,如下:

没有实现多态的单继承

[4]     0x01151433 {InterfaceDemo2.exe!CIF2::IF5(void)}     void *
     virtual void IF2() { cout<<"I'm IF2"<<endl; }
[5]     0x00000000     void *
public:

复制代码 代码如下:

    }
[0]     0x001e13d9 {InterfaceDemo2.exe!CIF1::IF1(void)}     void *
     void MemFunc(){ cout<<"I'm IF4"<<endl; }
 
        cout<<"I am A."<<endl;

学习C++的人,肯定都知道多态机制;多态就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。对于多态机制是如何实现的,你有没有想过呢?而COM中的接口就将这一机制运用到了极致,所以,不知道多态机制的人,是永运无法明白COM的。所以,在总结COM时,是非常有必要专门总结一下C++的多态机制是如何实现的。

class A


public:
};
 
[5]     0x001e1249 {InterfaceDemo2.exe!CIF3::IF6(void)}     void *
public:
     float m_fVar;
[1]     0x011512cb {InterfaceDemo2.exe!CIF1::IF2(void)}     void *


class A
{
#include <iostream>
using namespace std;

相关热词: C++

本站内容来源于网络,如有侵权请与我们联系,我们会及时删除,我们深感抱歉!
注:本站所有信息仅供用于网络技术学习参考,学习中请遵循相关法律法规!

本文地址: https://v30.fanwenzhu.com/jiaob/cjj/6114.shtml

最新文章
只需要在调用Ctrl+B编译后 只需要在调用Ctrl+B编译后

时间:2021-01-13

OpenGL超级宝典visual studio OpenGL超级宝典visual studio

时间:2021-01-04

Directx11 教程(2) 基本的wi Directx11 教程(2) 基本的wi

时间:2021-01-04

LeetCode11ContainerWithMostWate LeetCode11ContainerWithMostWate

时间:2021-01-04

C语言简单IT之家速成 C语言简单IT之家速成

时间:2020-12-27

三分钟了解Activity工作流 三分钟了解Activity工作流

时间:2020-12-27

编译器是如何实现32位整型 编译器是如何实现32位整型

时间:2020-12-27

C++中lower_bound函数和upper C++中lower_bound函数和upper

时间:2020-12-27

Copyright © www.juheyunku.com      关于 | 合作 | 声明 | 联系 | 更新 | 地图 | Tags

C++_C++ COM编程之接口背后的虚函数表,前言学习C++的人,肯定都知

2020-12-20 编辑:

好了,经过对上面代码的改造,就在A类的Print函数前面加入关键字virtual,具体代码如下:

复制代码 代码如下:

[6]     0x00000000     void *
class CIF

现在我在CIF2类中,重写CIF1类的IF1函数,它们的关系如下:

[0]     0x012014e2 {InterfaceDemo2.exe![thunk]:CIF3::IF1`adjustor{4}' (void)}     void *
public:


private:

多态

此时,代码的运行结果为:I am B.这个时候就表现出来了多态行为。好了,多了我也不说了,就通过这个简单的例子,你就能体会到多态的概念了。从下面才开始今天的主题。


在子类中重写了父类的虚函数,那它的虚函数表又是什么样子呢?

[3]     0x01151249 {InterfaceDemo2.exe!CIF2::IF4(void)}     void *
        cout<<"I am A."<<endl;

复制代码 代码如下:

复制代码 代码如下:

        cout<<"I am B."<<endl;
[5]     0x01151267 {InterfaceDemo2.exe!CIF2::IF6(void)}     void *
int main()

    void Print()
    {
class B : public A

        cout<<"I am B."<<endl;

虚函数表CIF1,如下:

[4]     0x012014d8 {InterfaceDemo2.exe!CIF3::IF5(void)}     void *

{

这个类的变量还没有被初始化时,就像上图那样,变量的值都是随机值,而指向虚拟函数表的指针__vfptr中对应的虚函数地址也是错误的地址;只有等我们真正的完成了这个变量的声明和初始化时,这些值才能被正确的初始化,如下图:

这样的一个类,当你去定义这个类的实例时,编译器会给这个类分配一个成员变量,该变量指向这个虚函数表,这个虚函数表中的每一项都会记录对应的虚函数的地址;如下图:

多态机制的关键就是在于虚函数表,也就是vtbl。当我们定义一个类,类中包含虚函数时,其实也就定义了一张虚函数表,没有虚函数的类是不包含虚函数表的,只有该类被实例化时,才会将这个表分配到这个实例的内存中;在这张虚函数表中,存放了每个虚函数的地址;它就像一个地图一样,指明了实际所应该调用的函数。比如我定义一个类,如下:

[4]     0x00b61433 {InterfaceDemo2.exe!CIF2::IF5(void)}     void *
[0]     0x00b61311 {InterfaceDemo2.exe!CIF2::IF1(void)}     void *
{


对于简单的,没有实现多态的多继承,比如,有下面的一个多继承关系:

总结了这么多关于虚函数表的内容,感觉很扯,和接口没有多大的关系;但是,这一切都是COM的基础,COM的背后,就是接口,而接口的背后,就是我这里总结的,说白了,完全了解了这里,对于理解COM的接口是有非常大的用处的。希望我的总结对大家有用。

}

     virtual void IF3() { cout<<"I'm IF3"<<endl; }

[6]     0x00000000     void *

你发现了什么?虚函数表中的第一项是CIF2::IF1,而不是CIF1::IF1,这说明了当在子类中重写父类的虚函数时,新的函数的地址覆盖了父类的虚函数地址,这样就能在多态时能正确的找到需要被调用的函数;而没有被覆盖的函数还是那样的顺序在虚函数表中存储着。

实现多态的单继承

    virtual void Print()
     virtual void IF1() { cout<<"I'm IF1"<<endl; }
[1]     0x012014ce {InterfaceDemo2.exe!CIF2::IF2(void)}     void *

可以发现,虚函数按照其声明顺序存放在表中,父类的虚函数在子类的虚函数前面。

[2]     0x01151343 {InterfaceDemo2.exe!CIF1::IF3(void)}     void *
[1]     0x00b612c6 {InterfaceDemo2.exe!CIF1::IF2(void)}     void *

在子类中没有重写任何父类的虚函数,那么它的虚函数表应该是什么样子呢?


};
    A *pAObj = new B();
    }


[2]     0x00b61343 {InterfaceDemo2.exe!CIF1::IF3(void)}     void *

没有实现多态的多继承

[3]     0x00b61249 {InterfaceDemo2.exe!CIF2::IF4(void)}     void *

    }

实现多态的多继承

复制代码 代码如下:

[2]     0x0120134d {InterfaceDemo2.exe!CIF1::IF3(void)}     void *

复制代码 代码如下:

 
[2]     0x012014d3 {InterfaceDemo2.exe!CIF2::IF3(void)}     void *
public:

[5]     0x00000000     void *

复制代码 代码如下:

};

比如有如下的继承关系:

};
    void Print()
[0]     0x012013cf {InterfaceDemo2.exe!CIF3::IF1(void)}     void *
[3]     0x01201456 {InterfaceDemo2.exe!CIF3::IF4(void)}     void *
[3]     0x00000000     void *
[1]     0x001e1447 {InterfaceDemo2.exe!CIF2::IF8(void)}     void *
};

在这个继承关系中,CIF2作为CIF1的子类,但是CIF2没有重写CIF1类的任何虚函数;定义CIF2 if2Obj;实例,在派生类的实例中,它的虚函数表应该是像下面这样的:
 

复制代码 代码如下:

using namespace std;
}
    {
int main()
[0]     0x011513c5 {InterfaceDemo2.exe!CIF1::IF1(void)}     void *
[0]     0x001e1258 {InterfaceDemo2.exe!CIF2::IF7(void)}     void *

什么是多态?上面也说了,多态就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。现在通过代码,让大家切身的体会一下多态:
[4]     0x001e1041 {InterfaceDemo2.exe!CIF3::IF5(void)}     void *
[3]     0x00000000     void *

上面说的是没有发生重写的情况,现在来说说发生重写的情况;比如,现在有以下情况:

[2]     0x001e127b {InterfaceDemo2.exe!CIF2::IF9(void)}     void *

上面代码的运行结果是:I am A.这不是多态的行为。

{
#include <iostream>
class B : public A

虚函数表CIF1,如下:

我们都知道,虚函数是用来支持C++中的多态的,而单独的一个类,有了虚函数,而没有任何继承关系,也就是说没有子类去覆盖父类的虚函数,这样是毫无意义的。所以下面就要从各个方面进行详细的说明虚函数表。

    }


 
     CIF(int i, int f) : m_iVar(i), m_fVar(f){}
[2]     0x001e1357 {InterfaceDemo2.exe!CIF1::IF3(void)}     void *

从上图中就可以看到,初始化完成以后,指向虚函数表的__vfptr指针中的元素都被赋予了正确的虚函数值,分别指向了在类中定义的三个虚函数。也看到了,__vfptr指针定义的位置也比m_iVar和m_fVar变量的位置靠前;在C++编译器中,它保证虚函数表的指针存在于对象实例中最前面的位置,这主要是为了在多层继承或是多重继承的情况下,能以高性能取到这张虚函数表,然后进行遍历,查找对应的虚函数指针,进行对应的调用。

虚函数表

    A *pAObj = new B();

从上面的虚函数表,我们可以分析出来,每个父类都有自己的虚函数表,子类的虚函数被放到了第一个父类的表中。第一个父类是按照声明顺序来判断的。

{


     int m_iVar;
 

    pAObj->Print();

虚函数表CIF2,如下:

[3]     0x001e10c8 {InterfaceDemo2.exe!CIF3::IF4(void)}     void *
    pAObj->Print();

总结

[1]     0x001e12df {InterfaceDemo2.exe!CIF1::IF2(void)}     void *
    void Print()

复制代码 代码如下:

    {
    {
{
[1]     0x012012d5 {InterfaceDemo2.exe!CIF1::IF2(void)}     void *

从上面的虚函数表中,我们可以看到虚函数表中的CIF1::IF1(void)全都被替换成了CIF3::IF1(void),那么我们就可以以任意的父类指针来调用IF1(void),实际上调用的是CIF3::IF1(void),这就实现了所谓的多态。

{
     CIF(){}

在上图中,CIF2继承了CIF1,并且在CIF2类中重写了CIF1的虚函数IF1,那我们现在看看虚函数表是什么样子的?

虚函数表CIF2,如下:

没有实现多态的单继承

[4]     0x01151433 {InterfaceDemo2.exe!CIF2::IF5(void)}     void *
     virtual void IF2() { cout<<"I'm IF2"<<endl; }
[5]     0x00000000     void *
public:

复制代码 代码如下:

    }
[0]     0x001e13d9 {InterfaceDemo2.exe!CIF1::IF1(void)}     void *
     void MemFunc(){ cout<<"I'm IF4"<<endl; }
 
        cout<<"I am A."<<endl;

学习C++的人,肯定都知道多态机制;多态就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。对于多态机制是如何实现的,你有没有想过呢?而COM中的接口就将这一机制运用到了极致,所以,不知道多态机制的人,是永运无法明白COM的。所以,在总结COM时,是非常有必要专门总结一下C++的多态机制是如何实现的。

class A


public:
};
 
[5]     0x001e1249 {InterfaceDemo2.exe!CIF3::IF6(void)}     void *
public:
     float m_fVar;
[1]     0x011512cb {InterfaceDemo2.exe!CIF1::IF2(void)}     void *


class A
{
#include <iostream>
using namespace std;

本站内容来源于网络,如有侵权请与我们联系,我们会及时删除,我们深感抱歉!
注:本站所有信息仅供学习参考!
本文地址为 https://v30.fanwenzhu.com/jiaob/cjj/6114.shtml

相关文章

风云图片

推荐阅读

返回C/C++频道首页