一.炼气期
1.1 多态与虚函数;
人们希望 cpp 存在一种抽象的引用(基类指针),跟随引用对象的类型变化,被引用对象的行为也不同;
#include <iostream>
using namespace std;
//父类
class Father1 {
public:
Father1() {
value = 10;
cout<<"father create \n"<<endl;
}
void speak() {
cout << "Father1::speak \n" << endl;
}
void run() {
cout << "Father1::run \n" << endl;
}
~Father1(){
cout << "Father1::~Father1 \n" << endl;
}
int value;
};
//子类
class SonObject :public Father1
{
public:
SonObject(){
cout << "son create" << endl;
}
~SonObject() {
cout << "SonObject::~SonObject" << endl;
}
void speak() { // 注意 没有用 virtual 修饰 speak // 是重写
cout << "son::speak" << endl;
}
void run() {
cout << "son::run" << endl;
}
};
int main()
{
SonObject s;
Father1 f1;
Father1 *f2 = static_cast<Father1 *>(&s);
s.speak();
f1.speak();
f2->speak();
return 0;
}
运行结果:
father create # s 中父类构造
son create # s 中子类构造
father create # f1 构造
son::speak # s.speak();
Father1::speak # f1.speak();
Father1::speak # f1.speak(); 不是虚函数, 没有多态
Father1::~Father1 # f1 析构
SonObject::~SonObject # s 析构
Father1::~Father1
将 speak 声明为虚函数后
#include <iostream>
using namespace std;
//父类
class Father1 {
public:
//... 其他成员 ...
virtual void speak() {
cout << "Father1::speak \n" << endl;
}
//... 其他成员 ...
};
//子类
class SonObject :public Father1
{
public:
//... 其他成员 ...
void speak() {
cout << "son::speak \n" << endl;
}
//... 其他成员 ...
};
int main()
{
SonObject s;
Father1 *f2 = static_cast<Father1 *>(&s);
f2->speak();
f2->run();
return 0;
}
运行结果:
father create
son create
son::speak # speak 是虚函数,有多态
Father1::run # run 没有多态,所以等价于 直接调用父类成员函数
SonObject::~SonObject
Father1::~Father1
1.2 基类 析构函数 最好是虚函数?
由上可知,如果 基类 析构函数 不是虚函数:
//父类
class Father1;
//子类
class SonObject :public Father1
{
void *pSonMem;
public:
SonObject() { // 比如 构造函数中 malloc 申请了内存
pSonMem = malloc(1);
}
~SonObject() { // 子类 希望在析构时释放 pSonMem 引用的内存
free(pSonMem);
}
//... 其他成员 ...
};
int main()
{
Father1 *f2 = static_cast<Father1 *>(new SonObject());
f2->speak();
f2->run();
delete(f2);
return 0;
}
运行结果:没有调用子类的析构函数,子类中 pSonMem 发生泄漏 !
father create
son create
son::speak
Father1::run
Father1::~Father1 # 只调用了父类的析构函数,没有多态的悲剧
【Tips】使用 override 可以检测出代码中意外的继承行为
class BaseClass{ virtual void funcA(); void funcD(); }; class DerivedClass: public BaseClass { virtual void funcA(); // ok, works as intended void funcD() override; };
编译报错 compiler error: funcD is nor exist or a virtual function
因为 BaseClass 的设计者就不希望 funcD 被重写
而 BaseClass 的使用者养成 override 的好习惯后就能避免 错误的 重写 (override)
二.筑基期
2.1 虚函数内存模型;
#include <iostream>
using namespace std;
//父类
class Father1 {
public:
Father1() {
value = 10;
cout<<"father create \n"<<endl;
}
virtual void speak() {
cout << "Father1::speak \n" << endl;
}
virtual void run() {
cout << "Father1::run \n" << endl;
}
~Father1(){
cout << "Father1::~Father1 \n" << endl;
}
int value;
};
//子类
class SonObject :public Father1
{
public:
SonObject(){
value = 10;
cout << "son create \n" << endl;
}
~SonObject() {
cout << "SonObject::~SonObject \n" << endl;
}
void speak() { // 子类 仅对 speak 进行重写
cout << "son :: speak \n" << endl;
}
};
int main()
{
SonObject s;
Father1 f1;
Father1 f2;
cout << "f1 addr " << ((int*)&f1) << endl // 父类 起始地址
<< "vtb addr :" << *(int **)(&f1) << endl // 父类 虚函数表(struct)指针
<< "vtb func0 addr " << *(*(int ***)(&f1)) << endl // 父类 虚函数表 虚函数0 入口 = Father1::speak
<< "vtb func1 addr " << *(*(int ***)(&f1)+1) << "\n\n"; // 父类 虚函数表 虚函数1 入口 = Father1::run
cout << "f2 addr " << ((int*)&f2) << endl // 父类 起始地址
<< "vtb addr :" << *(int **)(&f2) << endl // 父类 虚函数表(struct)指针
<< "vtb func0 addr " << *(*(int ***)(&f2)) << endl // 父类 虚函数表 虚函数0 入口 = Father1::speak
<< "vtb func1 addr " << *(*(int ***)(&f2)+1) << "\n\n"; // 父类 虚函数表 虚函数1 入口 = Father1::run
cout << "s addr " << ((int*)&s) << endl // 子类 起始地址
<< "&s.value : " << &s.value << endl // 子类 唯一数据成员地址
<< "vtb addr :" << *(int **)(&s) << endl // 子类 虚函数表(结构体)指针
<< "vtb func0 addr " << *(*(int ***)(&s)) << endl // 子类 虚函数表 虚函数0 入口 = SonObject::speak
<< "vtb func1 addr " << *(*(int ***)(&s)+1) << "\n\n"; // 子类 虚函数表 虚函数1 入口 = SonObject::run
return 0;
}
运行结果
father create
son create # s 构造完成
father create # f1 构造完成
father create # f2 构造完成
f1 addr 0xffffcf587528 # 父类 起始地址
vtb addr :0xaaaad0bcfce0 # 父类 虚函数表地址
vtb func0 addr 0xaaaad0bb122c # 父类 speak 入口
vtb func1 addr 0xaaaad0bb1264 # 父类 run 入口
f2 addr 0xffffcf587538 # 父类 起始地址
vtb addr :0xaaaad0bcfce0 # 父类 虚函数表地址 f2 与 f1 相同
vtb func0 addr 0xaaaad0bb122c # 父类 speak 入口
vtb func1 addr 0xaaaad0bb1264 # 父类 run 入口
s addr 0xffffcf587518 # 子类 起始地址
&s.value : 0xffffcf587520 # 子类 数据成员(地址)排在虚函数表指针之后
vtb addr :0xaaaad0bcfcc0 # 子类 虚函数表地址,不同于父类
vtb func0 addr 0xaaaad0bb13ac # 子类::speak 与 父类::speak 不相同
vtb func1 addr 0xaaaad0bb1264 # 子类::run 与 父类::run 相同
Father1::~Father1 # f2 析构
Father1::~Father1 # f1 析构
SonObject::~SonObject
Father1::~Father1 # f2 析构
2.2 构造函数不能是 虚函数 !;
虚函数表 是在 构造对象时分配内存后 才存在,如果 构造函数 也是虚函数,那么没有表(对应的那块内存),也就没有 虚函数?
先有鸡 还是先有蛋?
先有 “蛋”!对象被构造完了,才能从"蛋"里孵化出第一只鸡(虚函数表)!
三. 结丹期
3.1 多重继承
#include <iostream>
using namespace std;
//父类1
class Father1 {
public:
Father1() {
value = 10;
cout<<"father1 create"<<endl;
}
virtual void speak() {
cout << "Father1::speak" << endl;
}
~Father1(){
cout << "Father1::~Father1" << endl;
}
int value;
};
//父类2
class Father2 {
public:
Father2() {
value2 = 1;
cout<<"father2 create"<<endl;
}
// 如果再声明 virtual void speak() 编译器将报错:request for member ‘speak’ is ambiguous
void speak() {
cout << "Father2::speak" << endl;
}
virtual void run() {
cout << "Father2::run" << endl;
}
virtual void eat() {
cout << "Father2::eat" << endl;
}
~Father2(){
cout << "Father2::~Father2" << endl;
}
// int value; 与 Father1 成员重名,compiler error: request for member ‘value’ is ambiguous
int value2;
};
//子类 多重继承
class SonObject:public Father1,public Father2{
public:
virtual void speak() override {
cout << "SonObject::speak" << endl;
}
};
int main()
{
SonObject s;
Father1 f1;
Father2 f2;
Father1 *f1p = static_cast<Father1 *>(&s);
Father2 *f2p = static_cast<Father2 *>(&s);
cout << "f1 addr " << ((int*)&f1) << endl
<< "vtb addr :" << *(int **)(&f1) << endl
<< "vtb func0 addr " << *(*(int ***)(&f1)) << "\n---------\n";
cout << "f2 addr " << ((int*)&f2) << endl
<< "vtb addr :" << *(int **)(&f2) << endl
<< "vtb func0 addr " << *(*(int ***)(&f2)) << endl
<< "vtb func1 addr " << *((*(int ***)(&f2))+1) << "\n---------\n";
cout << "s addr " << ((int*)&s) << endl
<< "f1p :" << f1p << " sizeof(f1p) = " << sizeof(f1p) << endl
<< "vtb1 addr :" << *(int **)(&s) << endl
<< "father1 value addr :" << (int*)&s + 2 << " value :" << *((int*)&s + 2) << endl
<< "s.father1.vtb func0 addr " << *(*(int ***)(&s)) << endl
<< "f2p :" << f2p << endl
<< "vtb2 addr :" << *(int **)((Father1*)(&s) + 1) << endl //指针+1 => s的起始地址 向后偏移一个 Father1类 的大小
<< "s.father2.vtb func0 addr " << *(*(int ***)((Father1*)(&s) + 1)) << endl
<< "s.father2.vtb func1 addr " << *((*(int ***)((Father1*)(&s) + 1))+1) << "\n---------\n";
cout << "&s.value : " << &s.value << endl
<< "&(f1p->value) : " << &(f1p->value) << endl
<< "&(s.value2) : " << &s.value2 << endl
<< "&(f2p->value2) : " << &(f2p->value2) << "\n---------\n";
s.speak();
s.run();
f1p->speak(); // s 对 speak 的多态
f2p->speak(); // s 从 Father2 继承的 speak
return 0;
}
结果:
father1 create
father2 create # s 构造完成
father1 create # f1 构造完成
father2 create # f2 构造完成
f1 addr 0xffffcb3da5c8
vtb addr :0xaaaaac71fca8 # f1 虚函数表地址
vtb func0 addr 0xaaaaac70155c # Father1::speak
---------
f2 addr 0xffffcb3da5d8
vtb addr :0xaaaaac71fc88 # f2 虚函数表地址
vtb func0 addr 0xaaaaac701668 # Father2::run
vtb func1 addr 0xaaaaac7016a0 # Father2::eat
---------
s addr 0xffffcb3da5e8
f1p :0xffffcb3da5e8 sizeof(f1p) = 8 # 子类 Father1 虚函数表指针 存放的地址,即 this,一个虚函数表指针的大小 = 8字节
vtb1 addr :0xaaaaac71fc50 # 子类 Father1 虚函数表地址
father1 value addr :0xffffcb3da5f0 value :10 # 即 上述f1p + 0x08,紧贴在 子类 Father1 虚函数表指针 后面
s.father1.vtb func0 addr 0xaaaaac701720 # SonObject::speak 的入口 重写后 与 father1 不同
f2p :0xffffcb3da5f8 # 子类 Father2 虚函数表指针 存放的地址, 即 s 的起始地址 向后偏移 sizeof(Father1)
vtb2 addr :0xaaaaac71fc68 # 子类 Father2 虚函数表地址
s.father2.vtb func0 addr 0xaaaaac701668 # SonObject::run, 与 Father2::run 相同
s.father2.vtb func1 addr 0xaaaaac7016a0 # SonObject::eat, 与 Father2::eat 相同
---------
&s.value : 0xffffcb3da5f0
&(f1p->value) : 0xffffcb3da5f0
&(s.value2) : 0xffffcb3da600
&(f2p->value2) : 0xffffcb3da600
--------- # 多重继承内存模型,等价于 子类 按继承列表的顺序 依次拼接父类的内存模型;
SonObject::speak # s.speak();
Father2::run # s.run();
SonObject::speak # f1p->speak(); 多态
Father2::speak # f2p->speak(); 纯继承, Father2 中的 speak 不是虚函数,所以也不存在冲突
Father2::~Father2 # f2 析构完成
Father1::~Father1 # f1 析构完成
Father2::~Father2
Father1::~Father1 # s 析构完成
结论:
- 多重继承构造顺序 与 继承列表的顺序 相同;
- 多重继承内存模型,等价于 子类 按继承列表的顺序 依次拼接父类的内存模型;
- 如果父类 同名函数 不是虚函数,会被视作普通成员函数继承下来,不存在子对象的内存模型中;(上述 Father2::speak)
四. 元婴期
4.1 抽象类
在很多情况下,基类本身只是抽象概念,并没有实体(例如,“动物” 就只是一个抽象,现实世界中只有具体的 “猫“、”狗“)。
抽象类 应需求而生:包含纯虚函数的类称为抽象类,纯虚函数没有函数的实现,所以抽象类不能实例化。
抽象类 主要作为派生类提供一个公共的接口,派生类中对纯虚函数进行实现。