文章目录
一、virtual 虚函数工作原理
C++规定了虚函数的行为,但将实现方法留给了编译器
通常编译器处理虚函数的方法是: 给每个对象添加一个隐藏成员 __vfptr
,是一个指向函数数组(虚函数表 vftable
)的指针,虚函数表中存储的就是虚函数地址
二、虚函数内存布局
1.基类和派生类同名函数,不能被继承
#include <iostream>
using namespace std;
class Base {
public:
void func1() {
cout << "Base::func1" << endl;
}
int b;
};
class Derive : public Base {
public:
void func1(int i) {
cout << "Derive::func1" << endl;
}
int d;
};
int main() {
Base bObj;
Derive dObj;
cout << sizeof(Base) << " " << sizeof(Derive) << endl; // 4 8
bObj.func1(); // Base::func1
// dObj.func1(); // error C2660: 'Derive::func1': function does not take 0 arguments
dObj.func1(5); // Derive::func1
return 0;
}
运行结果如下:
当基类和派生类中函数同名但参数不同时,基类函数不能被派生类继承,派生类中并不构成重载
2.单继承,无虚函数覆盖,无新增虚函数
#include <iostream>
using namespace std;
class Base {
public:
virtual void func1() {
cout << "Base::func1" << endl;
}
virtual void func2() {
cout << "Base::func2" << endl;
}
int b;
};
class Derive : public Base {
public:
int d;
};
int main() {
Base bObj;
bObj.b = 1;
Derive dObj;
dObj.b = 2;
dObj.d = 3;
cout << sizeof(Base) << " " << sizeof(Derive) << endl; // 8 12
return 0;
}
i.基类的内存布局:
基类对象在内存中占用 8 个字节,一个是虚函数表指针 __vfptr,一个是成员变量 b
虚函数表占用 8 个字节,包括两个虚函数地址,虚函数表大小不计算在对象内
对象后面的 cccccccc 是为了分隔该对象和其它内容,虚函数表最后的四个字节均为0,表示虚表已经结束,不同系统不一样
ii.派生类的内存布局:
派生类对象在内存中占用 12 个字节,一个是虚函数表指针 __vfptr,一个是继承的成员变量 b,另一个是自己的成员变量 d
虚函数表占用 8 个字节,包括两个虚函数地址,与基类虚函数表中的函数地址一致,但和基类是两个独立的虚函数表
3.单继承,有虚函数覆盖,无新增虚函数
#include <iostream>
using namespace std;
class Base {
public:
virtual void func1() {
cout << "Base::func1" << endl;
}
virtual void func2() {
cout << "Base::func2" << endl;
}
int b;
};
class Derive : public Base {
public:
void func1() {
cout << "Derive::func1" << endl;
}
int d;
};
int main() {
Base bObj;
bObj.b = 1;
Derive dObj;
dObj.b = 2;
dObj.d = 3;
cout << sizeof(Base) << " " << sizeof(Derive) << endl; // 8 12
bObj.func1(); // Base::func1
bObj.func2(); // Base::func2
dObj.func1(); // Derive::func1
dObj.func2(); // Base::func2
return 0;
}
运行结果如下:
- 派生类重写的虚函数覆盖了虚函数表中基类的虚函数
- 派生类没有重写的虚函数仍然继承虚函数表中基类的虚函数
4.单继承,有虚函数覆盖,有新增虚函数
#include <iostream>
using namespace std;
class Base {
public:
virtual void func1() {
cout << "Base::func1" << endl;
}
virtual void func2() {
cout << "Base::func2" << endl;
}
int b;
};
class Derive : public Base {
public:
void func1() {
cout << "Derive::func1" << endl;
}
virtual void func3() {
cout << "Derive::func3" << endl;
}
int d;
};
int main() {
Base bObj;
bObj.b = 1;
Derive dObj;
dObj.b = 2;
dObj.d = 3;
cout << sizeof(Base) << " " << sizeof(Derive) << endl; // 8 12
bObj.func1(); // Base::func1
bObj.func2(); // Base::func2
dObj.func1(); // Derive::func1
dObj.func2(); // Base::func2
dObj.func3(); // Derive::func3
return 0;
}
运行结果如下:
- 派生类重写的虚函数覆盖了虚函数表中基类的虚函数
- 派生类没有重写的虚函数仍然继承虚函数表中基类的虚函数
- 派生类新增的虚函数添加到虚函数表的末尾
5.多继承,没有虚函数覆盖,没有新增虚函数
#include <iostream>
using namespace std;
class Base1 {
public:
virtual void func1() {
cout << "Base1::func1" << endl;
}
virtual void func2() {
cout << "Base1::func2" << endl;
}
int b1;
};
class Base2 {
public:
virtual void func3() {
cout << "Base2::func3" << endl;
}
virtual void func4() {
cout << "Base2::func4" << endl;
}
int b2;
};
class Derive : public Base1, public Base2 {
public:
int d;
};
int main() {
Base1 b1Obj;
Base2 b2Obj;
Derive dObj;
b1Obj.b1 = 1;
b2Obj.b2 = 2;
dObj.b1 = 3;
dObj.b2 = 4;
dObj.d = 5;
cout << sizeof(Base1) << " " << sizeof(Base2) << " " << sizeof(Derive) << endl; // 8 8 20
b1Obj.func1(); // Base1::func1
b1Obj.func2(); // Base1::func2
b2Obj.func3(); // Base2::func3
b2Obj.func4(); // Base2::func4
dObj.func1(); // Base1::func1
dObj.func2(); // Base1::func2
dObj.func3(); // Base2::func3
dObj.func4(); // Base2::func4
return 0;
}
运行结果如下:
- 派生类拥有多个虚函数表,个数与拥有虚函数的基类个数相同
- 每个虚函数表中的函数地址均与基类的虚函数地址一致
6.多继承,有虚函数覆盖,没有新增虚函数
#include <iostream>
using namespace std;
class Base1 {
public:
virtual void func1() {
cout << "Base1::func1" << endl;
}
virtual void func2() {
cout << "Base1::func2" << endl;
}
int b1;
};
class Base2 {
public:
virtual void func1() {
cout << "Base2::func1" << endl;
}
virtual void func2() {
cout << "Base2::func2" << endl;
}
virtual void func3() {
cout << "Base2::func3" << endl;
}
virtual void func4() {
cout << "Base2::func4" << endl;
}
int b2;
};
class Derive : public Base1, public Base2 {
public:
void func1() {
cout << "Derive::func1" << endl;
}
void func3() {
cout << "Derive::func3" << endl;
}
int d;
};
int main() {
Base1 b1Obj;
Base2 b2Obj;
Derive dObj;
b1Obj.b1 = 1;
b2Obj.b2 = 2;
dObj.b1 = 3;
dObj.b2 = 4;
dObj.d = 5;
cout << sizeof(Base1) << " " << sizeof(Base2) << " " << sizeof(Derive) << endl; // 8 8 20
b1Obj.func1(); // Base1::func1
b1Obj.func2(); // Base1::func2
b2Obj.func1(); // Base2::func1
b2Obj.func2(); // Base2::func2
b2Obj.func3(); // Base2::func3
b2Obj.func4(); // Base2::func4
dObj.func1(); // Derive::func1
dObj.Base1::func1(); // Base1::func1
dObj.Base2::func1(); // Base2::func1
// dObj.func2(); // error C2385: ambiguous access of 'func2'
dObj.Base1::func2(); // Base1::func2
dObj.Base2::func2(); // Base2::func2
dObj.func3(); // Derive::func3
dObj.func4(); // Base2::func4
return 0;
}
运行结果如下:
- 派生类拥有多个虚函数表,个数与拥有虚函数的基类个数相同
- 派生类重写的虚函数覆盖了所有虚函数表中基类的虚函数
- 派生类没有重写的虚函数仍然继承虚函数表中基类的虚函数
7.多继承,有虚函数覆盖,有新增虚函数
#include <iostream>
using namespace std;
class Base1 {
public:
virtual void func1() {
cout << "Base1::func1" << endl;
}
virtual void func2() {
cout << "Base1::func2" << endl;
}
int b1;
};
class Base2 {
public:
virtual void func1() {
cout << "Base2::func1" << endl;
}
virtual void func3() {
cout << "Base2::func3" << endl;
}
int b2;
};
class Derive : public Base1, public Base2 {
public:
void func1() {
cout << "Derive::func1" << endl;
}
virtual void func4() {
cout << "Derive::func4" << endl;
}
int d;
};
int main() {
Base1 b1Obj;
Base2 b2Obj;
Derive dObj;
b1Obj.b1 = 1;
b2Obj.b2 = 2;
dObj.b1 = 3;
dObj.b2 = 4;
dObj.d = 5;
cout << sizeof(Base1) << " " << sizeof(Base2) << " " << sizeof(Derive) << endl; // 8 8 20
b1Obj.func1(); // Base1::func1
b1Obj.func2(); // Base1::func2
b2Obj.func1(); // Base2::func1
b2Obj.func3(); // Base2::func3
dObj.func1(); // Derive::func1
dObj.func2(); // Base1::func2
dObj.func3(); // Base2::func3
dObj.func4(); // Derive::func4
return 0;
}
运行结果如下:
- 派生类拥有多个虚函数表,个数与拥有虚函数的基类个数相同
- 派生类重写的虚函数覆盖了所有虚函数表中基类的虚函数
- 派生类没有重写的虚函数仍然继承虚函数表中基类的虚函数
- 派生类新增的虚函数存放在继承的第一个基类的虚函数表中,并且添加到表的末尾
三、虚函数表指针访问
1.代码示例
#include <iostream>
using namespace std;
class Base1 {
public:
virtual void func1() {
cout << "Base1::func1" << endl;
}
virtual void func2() {
cout << "Base1::func2" << endl;
}
int b1;
};
class Base2 {
public:
virtual void func1() {
cout << "Base2::func1" << endl;
}
virtual void func3() {
cout << "Base2::func3" << endl;
}
int b2;
};
class Derive : public Base1, public Base2 {
public:
void func1() {
cout << "Derive::func1" << endl;
}
virtual void func4() {
cout << "Derive::func4" << endl;
}
int d;
};
int main() {
Base1 b1Obj;
Base2 b2Obj;
Derive dObj;
b1Obj.b1 = 1;
b2Obj.b2 = 2;
dObj.b1 = 3;
dObj.b2 = 4;
dObj.d = 5;
typedef void(*Func) (void);
int ** vftable = (int **)&dObj;
cout << "[0]Base1::__vfptr" << endl;
cout << " [0] "; ((Func)vftable[0][0])();
cout << " [1] "; ((Func)vftable[0][1])();
cout << " [2] "; ((Func)vftable[0][2])();
cout << "[1]Base1::b1" << endl;
cout << " [0] b1 = " << vftable[1] << endl;
cout << "[2]Base2::__vfptr" << endl;
cout << " [0] "; ((Func)vftable[2][0])();
cout << " [1] "; ((Func)vftable[2][1])();
cout << "[3]Base2::b2" << endl;
cout << " [0] b2 = " << vftable[3] << endl;
cout << "[4]Derive::d" << endl;
cout << " [0] d = " << vftable[4] << endl;
cout << "[5]End" << endl;
cout << " " << hex << vftable[5] << endl;
return 0;
}
/* output:
[0]Base1::__vfptr
[0] Derive::func1
[1] Base1::func2
[2] Derive::func4
[1]Base1::b1
[0] b1 = 00000003
[2]Base2::__vfptr
[0] Derive::func1
[1] Base2::func3
[3]Base2::b2
[0] b2 = 00000004
[4]Derive::d
[0] d = 00000005
[5]End
CCCCCCCC
*/
2.安全性
i. 通过基类指针访问派生类新增的虚函数
在上面的结果中可以看到 Base1::__vfptr
虚函数表中有派生类新增的虚函数,但我们不能通过基类指针来访问派生类新增的虚函数:
Base1 *b1 = new Derive();
b1->func4(); // 编译出错
妄图使用基类指针调用派生类中新增虚函数的行为都会被编译器视为非法,无法编译通过
但在运行时,由上面例子可见,可以访问虚函数表通过指针偏移的方式来实现,不过这样违反了C++的语义
ii. 访问 non-public 的虚函数
如果基类的虚函数是 private
或 protected
的,虚函数同样会存在于虚函数表中,同样可以访问虚函数表通过指针偏移的方式来实现,不过这样也违反了C++的语义
#include <iostream>
using namespace std;
class Base {
public:
virtual void func1() {
cout << "Base1::func1" << endl;
}
};
class Derive : public Base {
};
int main() {
typedef void(*Func) (void);
Derive dObj;
int ** vftable = (int **)&dObj;
((Func)vftable[0][0])(); // Base::func1
return 0;
}