多态是C++中一种代码复用的手段,什么叫多态?简单点说,就是多种形态。那么C++中是怎样实现多态的呢?
在C++中,多态分为两种:静态多态与动态多态。静态多态是对象声明的类型是在编译时确定的,比如说函数重载,模板。动态多态是所指对象的类型是在运行时确定的。动态多态的实现是通过虚函数来完成的。我们可以通过基类的指针或引用来调用基类与子类的函数,这个过程就实现了多态。我们知道,多态是通过虚函数来实现的,那虚函数的背后又做了哪些事情呢,本篇文章主要来探讨虚函数的背后-->虚函数表。
一.普通继承(有虚函数,有成员变量,无虚函数覆盖)
在此种继承中,子类只继承于一个父类,没有对父类的虚函数进行重写,子类与父类都有各自的成员变量。
#include<iostream>
using namespace std;
typedef void(*FUNC)();
void PrintVTable(int p)
{
int *ptr = (int*)p;
FUNC fun = NULL;
int i = 0;
while (ptr[i])
{
fun = (FUNC)ptr[i];
fun();
i++;
}
}
class Base
{
public :
Base()
:_b(10)
{}
virtual void func1()
{
cout << "Base::func1()" << endl;
}
virtual void func2()
{
cout << "Base::func2()" << endl;
}
private :
int _b;
};
class Deriver:public Base
{
public:
Deriver()
:_d(20)
{}
virtual void func3()
{
cout << "Deriver::func3()" << endl;
}
virtual void func4()
{
cout << "Deriver::func4()" << endl;
}
private:
int _d;
};
int main()
{
Base b;
Deriver d;
PrintVTable(*(int *)&b);
cout << "----------------" << endl;
PrintVTable(*(int *)&d);
getchar();
return 0;
}
我们可以通过监视窗口查看,大致的了解此种继承的对象模型。
通过以上分析,我们可以知道,多态的背后是依靠虚函数表去实现的,虚函数表里面存储的是每个虚函数的地址,顺序按照声明顺序排列。其中this指针里面保存的是虚函数指针的地址。
下面是派生类的对象模型:
从以上模型中我们可以得出以下结论:
1.在对象模型中,起始的四个字节用来存放虚函数指针,其指向虚函数表。
2.成员变量按照其继承与生命的顺序依次存放。
3.父类的虚函数放在子类的虚函数前面。
二.单继承(有成员变量,有虚函数,有虚函数覆盖)
在此种继承中,子类只有一个父类,子类的成员函数有对父类的成员函数进行了重写,有各自的成员变量。
#include<iostream>
using namespace std;
typedef void(*FUNC)();
void PrintVTable(int p)
{
int *ptr = (int*)p;
FUNC fun = NULL;
int i = 0;
while (ptr[i])
{
fun = (FUNC)ptr[i];
fun();
i++;
}
}
class Base
{
public :
Base()
:_b(10)
{}
virtual void func1()
{
cout << "Base::func1()" << endl;
}
virtual void func2()
{
cout << "Base::func2()" << endl;
}
private :
int _b;
};
class Deriver:public Base
{
public:
Deriver()
:_d(20)
{}
virtual void func1()
{
cout << "Deriver::func1()" << endl;
}
virtual void func3()
{
cout << "Deriver::func3()" << endl;
}
virtual void func4()
{
cout << "Deriver::func4()" << endl;
}
private:
int _d;
};
int main()
{
Base b;
Deriver d;
PrintVTable(*(int *)&b);
cout << "----------------" << endl;
PrintVTable(*(int *)&d);
getchar();
return 0;
}
我们可以通过调试窗口来观察子对象的大概模型。
下面是派生类的对象模型:
从以上模型中,我们可以得出以下结论:
当子类中有和父类同名的虚函数时,会形成覆盖,父类同名虚函数的地址会被子类同名函数的地址替换掉。此时用下面这种程序对派生类进行访问时,
Base *p=new Deriver;
p->func1();
调用的是派生类Deriver::func1(),即实现了多态。
三.多重继承(有成员变量,有虚函数,无虚函数覆盖)
#include<iostream>
using namespace std;
typedef void(*FUNC)();
void PrintVTable(int p)
{
int *ptr = (int*)p;
FUNC fun = NULL;
int i = 0;
while (ptr[i])
{
fun = (FUNC)ptr[i];
fun();
i++;
}
}
class Base1
{
public :
Base1()
:_b1(10)
{}
virtual void func1()
{
cout << "Base1::func1()" << endl;
}
private :
int _b1;
};
class Base2
{
public:
Base2()
:_b2(20)
{}
virtual void func2()
{
cout << "Base2::func2()" << endl;
}
private:
int _b2;
};
class Deriver:public Base1,public Base2
{
public:
Deriver()
:_d(30)
{}
virtual void func3()
{
cout << "Deriver::func3()" << endl;
}
virtual void func4()
{
cout << "Deriver::func4()" << endl;
}
private:
int _d;
};
int main()
{
Base1 b1;
Base2 b2;
Deriver d;
PrintVTable(*(int *)&b1);
cout << "----------------" << endl;
PrintVTable(*(int *)&b2);
cout << "----------------" << endl;
PrintVTable(*(int *)&d);
getchar();
return 0;
}
子类对象模型如下:
总结:
1.每个父类都有自己的虚表
2.子类的成员函数被放在第一个父类的虚函数表中
3.内存布局中,父类按照声明的顺序排列(可以将Base1与Base2的顺序调换做一下测试)
四.多重继承(有成员变量,有虚函数,有虚函数覆盖)
#include<iostream>
using namespace std;
typedef void(*FUNC)();
void PrintVTable(int p)
{
int *ptr = (int*)p;
FUNC fun = NULL;
int i = 0;
while (ptr[i])
{
fun = (FUNC)ptr[i];
fun();
i++;
}
}
class Base1
{
public :
Base1()
:_b1(10)
{}
virtual void func1()
{
cout << "Base1::func1()" << endl;
}
virtual void func2()
{
cout << "Base1::func2()" << endl;
}
private :
int _b1;
};
class Base2
{
public:
Base2()
:_b2(20)
{}
virtual void func1()
{
cout << "Base2::func1()" << endl;
}
virtual void func2()
{
cout << "Base2::func2()" << endl;
}
private:
int _b2;
};
class Deriver:public Base1,public Base2
{
public:
Deriver()
:_d(30)
{}
virtual void func1()
{
cout << "Deriver::func1()" << endl;
}
virtual void func3()
{
cout << "Deriver::func3()" << endl;
}
virtual void func4()
{
cout << "Deriver::func4()" << endl;
}
private:
int _d;
};
int main()
{
Base1 b1;
Base2 b2;
Deriver d;
PrintVTable(*(int *)&b1);
cout << "----------------" << endl;
PrintVTable(*(int *)&b2);
cout << "----------------" << endl;
PrintVTable(*(int *)&d);
getchar();
return 0;
}
从以上分析中,我们可以看出子类自己的成员函数是放在Base1的虚函数表里的,子类中重写的Deriver::func1()将Base1与Base2中的func1()都覆盖了。
子对象模型如下:
总结:
1.每个父类都有自己的虚表
2.子类的成员函数被放在第一个父类的虚函数表中
3.内存布局中,父类按照声明的顺序排列(可以将Base1与Base2的顺序调换做一下测试)
4.每个父类的func1()都被覆盖成了子类的func1(),保证了不同父类的指针指向 同一个子类实例,实现了多态。
五.重复继承
#include<iostream>
using namespace std;
typedef void(*FUNC)();
void PrintVTable(int p)
{
int *ptr = (int*)p;
FUNC fun = NULL;
int i = 0;
while (ptr[i])
{
fun = (FUNC)ptr[i];
fun();
i++;
}
}
class Base
{
public :
Base()
:_b(10)
{}
virtual void func1()
{
cout << "Base::func1()" << endl;
}
private :
int _b;
};
class Base1:public Base
{
public:
Base1()
:_b1(20)
{}
virtual void func1()
{
cout << "Base1::func1()" << endl;
}
virtual void func2()
{
cout << "Base1::func2()" << endl;
}
private:
int _b1;
};
class Base2:public Base
{
public:
Base2()
:_b2(30)
{}
virtual void func1()
{
cout << "Base2::func1()" << endl;
}
virtual void func3()
{
cout << "Base2::func3()" << endl;
}
private:
int _b2;
};
class Deriver:public Base1,public Base2
{
public:
Deriver()
:_d(40)
{}
virtual void func1()
{
cout << "Derive::func1()" << endl;
}
virtual void func4()
{
cout << "Deriver::func4()" << endl;
}
private:
int _d;
};
int main()
{
Base b;
Base1 b1;
Base2 b2;
Deriver d;
PrintVTable(*(int *)&b);
cout << "----------------" << endl;
PrintVTable(*(int *)&b1);
cout << "----------------" << endl;
PrintVTable(*(int *)&b2);
cout << "----------------" << endl;
PrintVTable(*(int *)&d);
getchar();
return 0;
内存分布如下:
子对象模型如下:
从以上模型我们可以看出,最顶端父类的数据成员被子类Deriver继承了两次,其中一次由Base1继承而来,另一次由Base2继承而来,而子类Deriver又继承于Base1与Base2 ,所以在子对象模型中,进行如下访问时,就会产生二义性。
Deriver d;
d._b=10;//产生二义性
d.Base1::_b=10;
d.Base2::_b=10;//正确
所以在子对象模型中,_d存储了两份,造成了数据冗余,这种继承我们成为重复继承。
六.重复虚拟继承(钻石继承)
重复虚拟继承是用来解决重复继承当中所造成的数据冗余问题,当在Base1和Base2的继承方式前加上virtual就是虚拟继承。
#include<iostream>
using namespace std;
typedef void(*FUNC)();
void PrintVTable(int p)
{
int *ptr = (int*)p;
FUNC fun = NULL;
int i = 0;
while (ptr[i])
{
fun = (FUNC)ptr[i];
fun();
i++;
}
}
class Base
{
public :
Base()
:_b(10)
{}
virtual void func1()
{
cout << "Base::func1()" << endl;
}
private :
int _b;
};
class Base1:virtual public Base
{
public:
Base1()
:_b1(20)
{}
virtual void func1()
{
cout << "Base1::func1()" << endl;
}
virtual void func2()
{
cout << "Base1::func2()" << endl;
}
private:
int _b1;
};
class Base2:virtual public Base
{
public:
Base2()
:_b2(30)
{}
virtual void func1()
{
cout << "Base2::func1()" << endl;
}
virtual void func3()
{
cout << "Base2::func3()" << endl;
}
private:
int _b2;
};
class Deriver:public Base1,public Base2
{
public:
Deriver()
:_d(40)
{}
virtual void func1()
{
cout << "Derive::func1()" << endl;
}
virtual void func4()
{
cout << "Deriver::func4()" << endl;
}
private:
int _d;
};
int main()
{
Base b;
Base1 b1;
Base2 b2;
Deriver d;
PrintVTable(*(int *)&d);
cout << "----------------" << endl;
PrintVTable(*(int *)&d+3);
cout << "----------------" << endl;
PrintVTable(*(int *)&d+8);
getchar();
return 0;
}
内存布局分析如下:
子对象模型为:
从上面我们可以看出重复虚拟继承的时候,最顶端的父类Base是在最下面存放的,并且Base1与Base2的虚函数表中并没有Base类的成员,其依靠一个地址来寻找Base的位置。
需要注意的是不同环境下,内存模型会有所不同,需要自己去测试,此处编译环境为vs2013。