首页 > 其他分享 >虚函数:什么时候我们真的需要它?

虚函数:什么时候我们真的需要它?

时间:2024-01-13 22:13:23浏览次数:26  
标签:需要 函数 函数调用 调用 foo 函数指针 真的 public

虚函数是函数指针的一种特殊的,可优化的语法糖,详见这个问题:https://stackoverflow.com/q/7046739/14033810

作为语法糖,它所做的让语言更简单,更安全的改进是限制了函数调用的范围。函数指针不再指向每一个类型匹配的函数(或任一个函数,如果bypass C的类型检查),而是被限制在只能调用一个类与其子类的同名函数里。
而更优化的点在于,通过type deduction,编译器将indirect call变成direct call,从而减少一次取地址的开销。

直观上,在可执行文件里,所有虚函数调用都可以被devirtualize,因为经过链接后,所有类的类型均已知,通过虚函数调用的真正函数已经全部可以通过人力推断。但事实上,仍然有必须使用vtable的场合:

class A 
{
public:
  virtual void foo();
}

class A1: public A
{
public:  
  void foo() override;
}

class A2: public A
{
public:
  void foo() override;
}

class B
{
private:
  A *a;
public:  
  virtual void foo() {a->foo();};
}

int main()
{
  A1 a1;
  A2 a2;
  B b1, b2;
  
  b1.a = &a1;
  b2.a = &a2;
  
  b1.foo();
  b2.foo();
}

在上例中,在链接结束后,我们可以充分地知道每个indirect call调用的具体函数是哪个。这使得b1.foob2.foo都没有必要再通过vtable实现(尽管在定义上它仍然是虚函数)。
然而,对a->foo而言则不然。由于B:foo()的代码只有一份,而这个函数指针可能指代多个函数,此时就必须以某种方式区分对多个可能的函数指针的访问,也就是vtable。在上例中,B::foo()必须通过vtable调用a->foo

判断是否能devirtualize似乎可以通过this指针的类型确定。在上例中,b1b2本身作为this指针时,调用一个自身定义的虚函数,此时有充足的信息进行判断。而在B::foo()中,由于this指针类型为B,而调用的函数为A::foo(),超出了这个类本身的定义,所以必须使用vtable。

讨论:

  1. 多态和动态链接的概念有很大的相似性,都属于“不知道地址是多少,在运行时确定”。然而,因为动态链接中函数的signature独一,函数指针指向的函数固定,所以基于PLT的调用是可以被完全消灭的,只是会牺牲动态链接带来的好处。而多态则不然,调用的函数不固定,带来的最大特点就是同一个call有多个可能调用的函数。因此,如果想要做到彻底的devirtualize,就必须为同一个函数引入多版代码,从而产生无理论上界的代码大小。

  2. 除去代码尺寸外,完全的devirtualization无法在一个pass内做完,继承链有几层,就要做几个pass,可能产生很大的时间开销。这件事可以在编译时做,也可以在运行时做。这理应是一个tradeoff。

  3. inline可以函数不同版本的问题。如上例中,虽然B::foo()的代码只有一份,但B::foo()的本质是调用this->a->foo(),因此把这一层函数调用去掉之后,此时this->a成为了新的this,又可以推断虚函数类型了。当然,函数是否inline是一个较复杂的话题,实现中使用了heuristic来确定,这里没有深究。一个合理的heuristic设计应当能发现inline这个函数能够消除虚函数调用,从而增加其被inline的概率。

标签:需要,函数,函数调用,调用,foo,函数指针,真的,public
From: https://www.cnblogs.com/orange-snow/p/17963074

相关文章

  • js slice()函数
    不包括"red",这是因为拆分操作要从位置1开始,即从"green"开始。得到的colors2数组包含1"green"、"blue"、"yellow"和"purple"。colors3数组是通过调用slice()并传入1和4得到的,即从位置1开始复制到位置3。因此colors3包含"green"、"blue"和&......
  • asp.net mvc4 controller构造函数
    asp.netmvc4controller构造函数ASP.NETMVC4中的Controller类有多种构造函数可供使用。以下是常见的两种构造函数示例:默认构造函数(无参):publicclassMyController:Controller{publicMyController(){}//这里为空的构造函数表示没有任何初始化操作}......
  • 函数返回值
    #获取数据的长度(不使用len)defgetnum(str):#str保存的是列表数据a=0#定义一个变量,保存for循环的次数foriinstr:#列表中有几个数就会循环几次a=a+1#每次循环的次数加1print(f'{str}的数据个数为{a}')#打印出列表数据以及长度str1=[1,2,3,......
  • ThreadLocal真的会造成内存泄漏吗?
    ThreadLoca在并发场景中,应用非常多。那ThreadLocal是不是真的会造成内存泄漏?今天给大家做一个分享,个人见解,仅供参考。1、ThreadLocal的基本原理简单介绍一下ThreadLocal,在多线程并发访问同一个共享变量的情况下,如果不做同步控制的话,就可能会导致数据不一致的问题,所以,我们需要使......
  • 9-函数基础
    '''函数的作用:当有一段功能代码需要重复使用,就定义函数具备某个作用的代码函数的使用:自定义函数:定义方式:def函数名():函数代码调用函数:调用函数才会执行函数内的代码函数名()#函数名其实保存的是函数的地址,加()才会调用函数的参数:当函数内部有不确定的数据值,应该......
  • 10-函数进阶-作用域
    '''定义的名字可以使用的区域在函数外面定义的名字,都属于全局名字在函数里面定义的名字,都属于局部名字局部作用域:全局作用域:python自带名字(函数名和变量名)内置区域函数外面定义的名字:全局区域函数里面定义的变量:局部区域'''a=1#全局变量deff():a=2#局部......
  • 11-函数进阶-参数类型
    deffunc(a,b):print(a,b)func(1,2)#实参的个数和形参的个数必须要保持一致func(1)func(1,2,3)func()位置参数默认参数,在调用函数的过程中,多次调用传入的实参一致,可以将参数定义为默认参数deffunc(a,b=1):#语法要求:如果形参中包含位置参数和默认参数,位置参数要放在最......
  • python回调函数
    回调函数在各种编程函数中都是比较常见的,回调函数的出现主要是为了降低函数之间调用的耦合性,从而实现解耦。简单来说,如果一个函数可以作为被传递就称这个函数为回调函数。比如:func1(func2()),那么func2就是回调函数。此时,只需要知道func2这个函数的功能是什么,在执行func1时调用fu......
  • Shell编程自动化之if、for、while和函数
    一、if语句1.单分支格式if[条件判断式];then当条件判断成立时,执行的命令内容fiif[条件判断式]then当条件判断成立时,执行的命令内容fi2.双分支格式if[条件判断式];then当条件判断成立时,执行的命令内容else当条件判断......
  • 函数计算域名调试web应用
    函数计算域名调试web应用如果没有域名的话,可以利用一个小技巧来绕过阿里云对于函数计算域名使用的限制,从而直接使用阿里云的域名进行访问先进入如下页面复制公网访问地址​​然后安装可以修改响应头的浏览器插件,例如https://github.com/FirefoxBar/HeaderEditor,然后进行类......