1 虚函数,虚表基础
虚函数,虚表基础
2 虚函数表保存在哪里 ?
虚函数表在编译的时候确定。在 linux 下,保存在只读数据段的重定位段,这个段的名字是 .data.rel.ro。
如下代码,编译之后,使用 readelf -t a.out 可以查看看到这一段的大小是 0x68,如果从代码中删除一个虚函数,比如把函数 Do2() 不用 virtual 修饰,可以看到这个段的大小减小了 16。由此,可以验证虚函数表保存在了 .data.rel.ro。
#include <iostream>
#include <string>
#include <stdlib.h>
#include <stdio.h>
class Base {
public:
Base() {
}
~Base() {
}
virtual void Do1(int d) {
std::cout << d << std::endl;
}
virtual void Do2(int d) {
std::cout << d << std::endl;
}
};
class Derived : public Base {
public:
Derived() {
}
~Derived() {
}
virtual void Do1(int d) {
std::cout << d << std::endl;
}
virtual void Do2(int d) {
std::cout << d << std::endl;
}
};
int main() {
Base *b = new Derived();
b->Do1(1);
b->Do2(2);
delete b;
return 0;
}
将 Base() 和 Derived() 中的函数 Do2() 不用 virtual 修饰,可以看到 .data.rel.ro 的大小减小了 16。
3 基类构造函数和析构函数中调用的虚函数是基类的还是派生类的 ?
基类构造函数和析构函数中调用的虚函数是基类的还是派生类的 ?
3 哪些函数不能声明为虚函数 ?
3.1 构造函数
构造函数声明为虚函数,编译错误如下。
访问虚函数首先要通过对象的地址找到虚函数表的地址,然后找到对应的虚函数来执行。也就是说虚函数的调用依赖于对象。而调用构造函数的时候,这个对象还不存在,所以构造函数不能声明为虚函数。
3.2 静态函数
静态函数声明为虚函数,编译错误如下。
与构造函数类似,静态函数中也没有 this 指针,所以静态函数不能声明为虚函数。
3.3 函数模板
函数模板不能声明为虚函数。编译报错如下。虚函数表需要在类定义的时候就要确定,对于函数模板来说,类定义的时候,这个函数有多少实例是不确定的,所以函数模板不能定义为虚函数。
#include <iostream>
#include <string>
#include <stdlib.h>
#include <stdio.h>
class Base {
public:
Base() {
}
~Base() {
}
template <class T>
virtual void Do1(T d) {
std::cout << d << std::endl;
}
void Do2(int d) {
std::cout << d << std::endl;
}
};
int main() {
Base *b = new Base();
b->Do1(1);
b->Do2(2);
delete b;
return 0;
}
函数模板不能声明为虚函数。但是类模板中的函数可以声明为虚函数,因为对于类模板来说,在定义一个实例的时候,这个类的定义就是确定的,虚函数表的大小也是确定的。
#include <iostream>
#include <string>
#include <stdlib.h>
#include <stdio.h>
template <class T>
class Base {
public:
Base() {
}
~Base() {
}
virtual void Do1(T d) {
std::cout << d << std::endl;
}
void Do2(int d) {
std::cout << d << std::endl;
}
};
int main() {
Base<int> *b = new Base<int>();
b->Do1(1);
b->Do2(2);
delete b;
return 0;
}
4 inline 函数可以声明为虚虚函数
如下代码,Base 是基类,其中有一个 inline 虚函数 Do1()。Derived1 和 Derived2 是 Base 的派生类。分别创建 Base、Derived1、Derived2 的一个对象,然后通过指针调用函数 Do1()。代码编译没有问题,运行也没有问题。
#include <iostream>
#include <string>
#include <stdlib.h>
#include <stdio.h>
class Base {
public:
virtual ~Base() {
}
virtual inline void Do1(int d) {
std::cout << d << std::endl;
}
};
class Derived1 : public Base{
public:
virtual inline void Do1(int d) {
std::cout << d << std::endl;
}
};
class Derived2 : public Base{
public:
virtual inline void Do1(int d) {
std::cout << d << std::endl;
}
};
void Do(Base *b, int data) {
b->Do1(data);
}
int main() {
Base *b = new Base();
Base *b1 = new Derived1();
Base *b2 = new Derived2();
Do(b, 10);
Do(b1, 20);
Do(b2, 30);
return 0;
}