C++ 由虚基类 虚继承 虚函数 到 虚函数表

时间:2021-08-21 05:37:58
//虚基类:一个类可以在一个类族中既被用作虚基类,也被用作非虚基类。
class Base1{
public:
Base1(){cout<<"Construct Base1!"<<endl;};
void foo();//普通函数
virtual void foo1(){cout<<"foo1 in Base1"<<endl;};//虚函数:可以在基类中实现(+{})或者直接定义成虚基类,\
会出现错误:undefined reference to vtable for lass
virtual void foo2() = 0;//纯虚函数:必须要在继承类中实现,类似java中的接口好处是 可以在不清楚具体构建的情况下,给派生类规定好规范
}; class Base2{
public:
void foo();//普通函数
virtual void foo1();//虚函数
//virtual void foo2() = 0;//纯虚函数
};
//虚继承:继承类继承了基类多次,从而产生了多个拷贝,虚基类的基本原则是在内存中只有基类成员的一份拷贝。\
//这样,通过把基类继承声明为虚拟的,就只能继承基类的一份拷贝,从而消除歧义。
class Derived1: public virtual Base1{
public:
Derived1(){cout<<"Construct Derived1~ !"<<endl;};
void foo2(){
cout<<"foo2 in Derive1"<<endl;
}
};
//多继承
class Derived2: public virtual Base1, public virtual Base2{
void foo2(){
cout<<"foo2 in Derive1"<<endl;
}
};
错误解决:

在使用虚函数的程序中,编译时会出现

      undefined reference to `vtable for Class 

或    undefined reference to typeinfo for Class   的情况

其解决方案就是将类似于

virtual void foo();
Should be defined (inline or in a linked source file): virtual void foo() {}
Or declared pure virtual: virtual void foo() = 0;
 

基类指针指向派生类  

基类指针可以指向派生类,但是无法使用不存在于基类只存在于派生类的元素。(所以我们需要虚函数和纯虚函数)

  在内存中,一个基类类型的指针是覆盖N个单位长度的内存空间。
  当其指向派生类的时候,由于派生类元素在内存中堆放是:前N个是基类的元素,N之后的是派生类的元素。
  于是基类的指针就可以访问到基类也有的元素了,但是此时无法访问到派生类(就是N之后)的元素。

派生类指针不能指向基类

虚函数表

  本文部分内容参考了 陈皓专栏的内容.

  对c++了解的人都应该知道虚函数是通过一张虚函数表实现的。简称V-Table。 在这个表中,主要是一个类的虚函数的地址表,这张表解决了 继承、覆盖的问题,保证内容真实反映实际的函数。这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子 类的时候,这张虚函数表就显得尤为重要了,它就像一个地图一样,指明了实际所应该调用的函数。C++的编译器保证虚函数表的指针存在与对象实例中的最前面 的位置(这是为了保证取道虚表函数的高性能)。 

  在每一个包含虚函数的类中,都存在一张虚函数表,用来记录虚函数真正的地址。当派生类继承于基类时,将基类的虚函数表也一同继承,然后根据自己是否覆盖基类的函数,来修改虚函数表。具体看这个:讲的很详细。实际上是每一个类都会创建一张虚表,每一个类的实例也就从类中得到了一张虚表.

  

  一般: 无覆盖

C++ 由虚基类 虚继承 虚函数 到 虚函数表

  一般继承: 虚函数覆盖

C++ 由虚基类 虚继承 虚函数 到 虚函数表

  多重继承:无覆盖

  C++ 由虚基类 虚继承 虚函数 到 虚函数表

C++ 由虚基类 虚继承 虚函数 到 虚函数表

  可以看到子类函数的虚表就接在第一个基类虚表的后面

  多重继承: 有虚函数覆盖

C++ 由虚基类 虚继承 虚函数 到 虚函数表

需要注意的问题:

  不能通过父类型的指针访问子类自己的函数。即使这种目的可以使用指针的方式访问来达到

  包含任一纯虚函数的类 为 抽象类。抽象类是不能够实例化的。

  

#include <iostream>
#include <cstdio>
using namespace std; class Base{
public:
virtual void f(){ cout<<" Base::f "<<endl; }
virtual void g(){ cout<<" Base::g "<<endl; }
virtual void h(){ cout<<" Base::h "<<endl; }
}; class Base1{
public:
virtual void f(){ cout<<" Base1::f "<<endl; }
virtual void g(){ cout<<" Base1::g "<<endl; }
virtual void h(){ cout<<" Base1::h "<<endl; }
}; class Base2{
public:
virtual void f(){ cout<<" Base2::f "<<endl; }
virtual void g(){ cout<<" Base2::g "<<endl; }
virtual void h(){ cout<<" Base2::h "<<endl; }
}; class Base3{
public:
virtual void f(){ cout<<" Base3::f "<<endl; }
virtual void g(){ cout<<" Base3::g "<<endl; }
virtual void h(){ cout<<" Base3::h "<<endl; }
}; class Derive : public Base{
public:
virtual void f1(){ cout<<" Derive::f1 "<<endl; }
virtual void g1(){ cout<<" Derive::g1 "<<endl; }
virtual void h1(){ cout<<" Derive::h1"<<endl; }
}; class Derive1 : public Base{
public:
virtual void f(){ cout<<" Derive1::f"<<endl; }
virtual void g1(){ cout<<" Derive1::g1 "<<endl; }
virtual void h1(){ cout<<" Derive1::h1"<<endl; }
}; class Derive2 : public Base1, public Base2, public Base3{
public:
virtual void f1(){ cout<<" Derive2::f1"<<endl; }
virtual void g1(){ cout<<" Derive2::g1 "<<endl; }
}; class Derive3: public Base1, public Base2, public Base3{
public:
virtual void f(){ cout<<" Derive3::f"<<endl; }
virtual void g1(){ cout<<" Derive3::g1 "<<endl; }
}; typedef void(*Fun)(void);
//作为函数指针 你得规定好指向的函数的参数类型,返回值类型. int main(void)
{
Base b;
Fun pFun = NULL;
cout<<"虚函数表地址: "<<&b<<endl;
cout<<"虚函数表地址: "<<(void *)&b<<endl;
cout<<"虚函数表地址: "<<(int*)&b<<endl;
cout<<"虚函数表地址: "<<(long*)&b<<endl;
/*上面四种输出结果一致 , b的地址转换成何种类型的指针看似是无所谓*/
cout<<"虚函数表---第一个函数地址: "<<(int *)*(int *)(&b)<<endl;
cout<<"虚函数表---第一个函数地址: "<<(void *)*(int*)(&b)<<endl;
/*
* long:在32位系统是32位整型,取值范围为-2^31 ~ (2^31 - 1);在64位系统是64位整型,取值范围为-2^63 ~ (2^63 - 1)
* 为了能在64位机器上运行,这里要用 long 而不是int
*/
pFun = (Fun)*((long *)*(long*)(&b)); //Base::f
pFun = (Fun)*((long *)*(long*)(&b) + ); //Base::g
pFun = (Fun)*((long *)*(long*)(&b) + ); //Base::h
/* 我们就可以通过上面的方法调用函数 */ /*
*虚函数表明明显是在子类继承父类的时候能够发挥作用, 这里继承可以分为两类:
* 一般继承(无虚函数覆盖 & 有虚函数覆盖) 和 多重继承(有 & 无 虚函数覆盖)
*/ /* 一般: 无覆盖*/
Derive d;
pFun = (Fun)*((long *)*(long*)(&d) + ); //Derive::f1 /*
* 1)虚函数按照其声明顺序放于表中。
* 2)父类的虚函数在子类的虚函数前面。
*/ /* 一般继承: 有虚函数覆盖 */
Derive1 d1;
pFun = (Fun)*((long *)*(long*)(&d1)); //Derive::1
pFun = (Fun)*((long *)*(long*)(&d1) + ); //Base::g
/*
* 1)覆盖的f()函数被放到了虚表中原来父类虚函数的位置。
* 2)没有被覆盖的函数依旧。
*/ /* 多重继承: 无覆盖*/
Derive2 d2;
pFun = (Fun)*((long *)*(long *)(&d2)); //Base::f
pFun = (Fun)*((long *)*(long *)(&d2) + ); //Derive2::f
pFun = (Fun)*((long *)*( (long *)(&d2) + )); // Base2: f 这里*是取地址得到了虚表上第一个虚函数表Base1中第一个函数的地址 ; + 1 得到第二个虚函数表Base2中第一个函数的地址;
/*
*1) 每个父类都有自己的虚表。
*2) 子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)
*/ /* 多重继承: 有虚函数覆盖*/
Derive3 d3;
pFun = (Fun)*((long *)*( (long *)(&d3) ) ); //Derive3: f
pFun = (Fun)*((long *)*( (long *)(&d3) + )); // Derive3: f
pFun = (Fun)*((long *)*( (long *)(&d3) + )); // Derive3: f 这里*是取地址得到了虚表上第一个虚函数表Base1中第一个函数的地址 ; + 1 得到第二个虚函数表Base2中第一个函数的地址;
/*这里设计到了虚表的概念,也就是每一个基类在子类的虚表中都占有一席 , 我们当然可以通过上面这行code来访问其他虚表,
*但是还可以用二维数组来访问,更加便捷
*/
long **pVtab = (long **)&d3;
//Base1 's vtable
pFun = (Fun)pVtab[][];//Derive3::f
pFun = (Fun)pVtab[][];//Base::g
//Derive's vtable
pFun = (Fun)pVtab[][]; //Derive3::g1
//The tail of the vtable
pFun = (Fun)pVtab[][]; /*
*三个父类虚函数表中的f()的位置被替换成了子类的函数指针。
*我们可以看见,三个父类虚函数表中的f()的位置被替换成了子类的函数指针。
*这样,我们就可以任一静态类型的父类来指向子类,并调用子类的f()了。如:
*/
Base1 *b1 = &d3;
Base2 *b2 = &d3;
Base3 *b3 = &d3;
b1->f(); //Derive3::f()
b2->f(); //Derive3::f()
b3->f(); //Derive3::f()
b1->g(); //Base1::g()
b2->g(); //Base2::g()
b3->g(); //Base3::g()
/* 当然,父类指针只能指向子类中覆盖的父类中的虚函数, 如下,尝试访问子类中自有的函数 会报错*/
Base1 *b4 = new Derive3();
//b4 -> g1();///error: ‘class Base1’ has no member named ‘g1’| /* 但是并不安全:
* 1) 通过父类型的指针访问子类自己的虚函数: 在运行时,我们可以通过指针的方式访问虚函数表来达到违反C++语义的行为。
* 2) 访问non-public的虚函数:
*/ return ;
}

#########################################################################################################

#再说C++多态 #

#########################################################################################################

.  “C++的多态性用一句话概括就是:在基类的函数前+vritual关键字,在派生类中重写该函数,运行时会根据对象的实际类型来调用相应的函数(晚绑定或者叫动态绑定)。如果对象类型是基类,那么调用基类的函数。如果是派生类,那么调用派生类的函数。”

  1:用virtual关键字申明的函数叫做虚函数,虚函数肯定是类的成员函数。

  2:存在虚函数的类都有一个一维的虚函数表叫做虚表,类的对象有一个指向虚表开始的虚指针。虚表是和类对应的,虚表指针是和对象对应的。

  3:多态性是一个接口多种实现,是面向对象的核心,分为类的多态性和函数的多态性。

  4:多态用虚函数来实现,结合动态绑定.

  5:纯虚函数是虚函数再加上 = 0;

  6:抽象类是指包括至少一个纯虚函数的类。

  纯虚函数:virtual void fun()=0;即抽象类!必须在子类实现这个函数,即先有名称,没有内容,在派生类实现内容。

   编译器在编译的时候,发现Base类中有虚函数,这时候编译器会为每个包含虚函数的类创建一个虚表(vtable),该表是一个一维数组,在这个数组中存放每一个函数的地址。

  如何定位虚表?编译器提供另外一个结构虚指针(vptr)。虚指针指向虚表,虚表中存放的是函数的地址。在程序运行时,根据对象的类型去初始化vptr,从而让vptr指向正确的所属类的虚表。由于pFather实际指向的对象类型是Son,所以vptr指向Son的vtable。