基类构造函数和析构函数中调用的虚函数是基类的还是派生类的 ?

(1)基类中的构造函数调用虚函数,调用的是基类自己的虚函数

① 在编译的时候,函数调用就是静态编译的,而不是查虚函数表

② 在构造派生类时,需要先构造基类,再构造派生类,在构造基类的时候,派生类还没有构造,没有 this 指针。而虚函数的调用需要查虚函数表,需要通过 this 指针来查找

(2)基类中的析构函数调用虚函数,调用的是基类自己的虚函数

① 在编译的时候,函数调用就是静态编译的,而不是查虚函数表

② 析构的时候,先析构派生类,再析构基类,在调用基类析构函数的时候,派生类已经被析构,所以无法查虚函数表

(3)其它函数中调用虚函数,需要通过查虚函数表来调用

 

1 基类中调用的虚函数

如下代码,Base 是基类,其中有 3 个虚函数 Do1(),Do2() 和 Do3(),另外还有一个非虚函数 Do()。Derived 是继承 Base 的派生类,在 Derived 中覆写了 Do1(),Do2() 和 Do3()。在 main() 函数中创建了一个 Derived 对象,并把指针赋值给 Base 类型的指针 b。

使用 b 调用 Do1(),Do2() 和 Do3() 的时候,我们都知道调用的是 Derived 中的函数。使用 b 调用 Do() 的时候,函数 Do() 中调用了一个虚函数 Do1(),那么这里调用的 Do1() 是基类 Base 中的函数还是派生类 Derived 中的函数呢 ?  调用的是派生类中的 Do1()。

#include <iostream>
#include <string>

class Base {
public:
  void Do() {
    std::cout << "Base() Do()\n";
    Do1();
  }

  virtual void Do1() {
    std::cout << "Base() Do1()\n";
  }

  virtual void Do2() {
    std::cout << "Base() Do2()\n";
  }

  virtual void Do3() {
    std::cout << "Base() Do3()\n";
  }
};

class Derived : public  Base {
public:
  virtual void Do1() {
    std::cout << "Derived() Do1()\n";
  }

  virtual void Do2() {
    std::cout << "Derived() Do2()\n";
  }

  virtual void Do3() {
    std::cout << "Derived() Do3()\n";
  }
};

int main() {
  Base *b = new Derived;
  b->Do();
  std::cout << "----------------\n";
  b->Do1();
  b->Do2();
  b->Do3();
  return 0;
}

运行结果如下:

代码编译之后使用 objdump -d -S -l 来对 a.out 进行反汇编。

(1)调用非虚函数 Do() 的时候,在汇编代码中是通过函数符号来表示的。通过符号表硬编码,调用的是父类中的 Do()。

(2)在调用虚函数 Do1(),Do2() 和 Do3() 的时候,并不是通过符号来调用的。虚函数的调用是通过查虚表来调用的,从汇编指令中也可以看到,虚函数 Do1() 在虚函数表的第一个位置;虚函数 Do2() 在虚函数表的第二个表项,偏移 0x8;虚函数 Do3() 在虚函数表的第三个表项,偏移 0x10。

(3)在 Do() 中调用 Do1(),也是从虚函数表中找到虚函数,然后调用。

b 指向 Derived 类型的指针,其中的虚函数表是类 Derived 的,所以在 Do() 中调用的 Do1() 是 Derived 中的 Do1()。

2 基类的构造函数和析构函数调用虚函数

2.1 基类构造函数调用虚函数

如下代码 Base 是基类,Derived 是派生类。在基类 Base 的构造函数中调用虚函数 Do1(),那么调用的是基类的 Do1() 还是派生类的 Do1() 呢 ?

基类构造函数调用的虚函数是基类中的虚函数。

派生类构造函数调用的虚函数是派生类中的虚函数。

如下代码,Base 是基类,Derived 是派生类。在基类的构造函数中调用了虚函数 Do1(),在派生类的构造函数中调用了虚函数 Do1()。

#include <iostream>
#include <string>
#include <stdlib.h>
#include <stdio.h>

class Base {
public:
  Base() {
    std::cout << "Base()\n";
    Do1();
    printf("Base(), vfptr = %p\n", (unsigned long *)*(unsigned long *)(void *)this);
  }

  virtual void Do1() {
    std::cout << "Base() Do1()\n";
  }

  void PrintVfptr() {
    printf("Base, vfptr = %p\n", (unsigned long *)*(unsigned long *)(void *)this);
  }
};

class Derived : public  Base {
public:
  Derived() {
    std::cout << "Derived()\n";
    Do1();
    printf("Derived(), vfptr = %p\n", (unsigned long *)*(unsigned long *)(void *)this);
  }

  virtual void Do1() {
    std::cout << "Derived() Do1()\n";
  }
};

int main() {
  Base *b = new Derived;
  b->PrintVfptr();
  return 0;
}

运行结果如下:

(1)从现象来看,可以看出基类的构造函数调用的虚函数是基类中实现的函数;派生类的构造函数中调用的虚函数是派生类中实现的虚函数。

(2)在基类和派生类的构造函数中打印了类的虚函数表。可以看出,在基类构造函数中的虚函数表是基类自己的虚函数表。

从下边汇编代码也可以看出来,在构造函数中调用虚函数,并没有查虚函数表,而是通过函数符号直接调用的本类的函数。

2.2 基类析构函数调用虚函数

从汇编代码中也可以看出来,基类析构函数中调用的是基类的虚函数,派生类析构函数中调用的是派生类的虚函数。

#include <iostream>
#include <string>
#include <stdlib.h>
#include <stdio.h>

class Base {
public:
  Base() {
    std::cout << "Base()\n";
  }

  virtual ~Base() {
    std::cout << "~Base()\n";
    Do1();
  }

  virtual void Do1() {
    std::cout << "Base() Do1()\n";
  }
};

class Derived : public  Base {
public:
  Derived() {
    std::cout << "Derived()\n";
  }

  ~Derived() {
    std::cout << "~Derived()\n";
    Do1();
  }

  virtual void Do1() {
    std::cout << "Derived() Do1()\n";
  }
};

int main() {
  Base *b = new Derived;
  delete b;
  return 0;
}

  • 5
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值