目录

虚函数

虚函数

  1. 虚函数的种类

    • non-virtual: 你不希望派生类重新定义

    • virtual:你希望派生类重新定义,且他有默认定义

    • pure virtual:你希望派生类一定要重新定义,你对他没有默认定义

      1
      2
      3
      4
      5
      6
      
      class Shape {
      public:
        virtual void draw() const = 0; // pure virtual
        virtual void error(const std::string &msg); // impure virtual
        int objectID() const; // non-virtual
      };
      

虚函数的实现原理

  1. 虚表与虚指针

    • 我们先把下述代码扫一遍,并且来看看图,我们可以看到最左边的表 上面记录着 继承的父类和 自己的数据,但是除了这些之外,是不是还有一个记录着内存地址的,这个就是虚指针(vtpr)这个虚指针指向着一张虚表(vtbl)

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      
      class A {
      pubilc:
          virtual void vfunc1();
        virtual void vfunc2();
        void func1();
        void func2();
      private:
        int m_data1, m_data2;
      };
      
      class B : public A {
      pubilc:
          virtual void vfunc1();
        void func2();
      private:
        int m_data3;
      };
      
      class C : public B {
      pubilc:
          virtual void vfunc1();
        void func2();
      private:
        int m_data1, m_data4;
      };
      

      https://raw.githubusercontent.com/vlicecream/cloudImage/main/data/imagesimage-20220521213437372.png

  2. c++对一个虚函数进行调用的过程

    • 他首先会考虑是静态绑定还是动态绑定
    • 静态绑定 就是 call xxx call(汇编语言的一个动作) xxx(addr)
    • 如果是满足三个条件就会动态绑定
      • 通过指针来调用
      • 指针满足向上转型
      • 调用的是虚函数
      • 只要满足这三个条件 就会变成 (*(p->vptr)[n])(p); 或者 (*p->vptr[n] )(p);
      • 为什么叫动态绑定,因为要看"p"是什么

虚函数的妙用

  1. 通过虚指针我们可以实现多态

    • 比如 一个list 需要放多个不同大小的 比如 矩形 圆形 长方形 各种的 就可以直接放父类A 的指针 指向不同大小的 具体图形 因为对于指针来说 都是4个byte

      1
      
      list<A*> myList;
      

虚函数的注意点

  1. 析构函数要写成虚函数

    • 是为了降低内存泄漏的可能性
    • 一个基类的指针指向一个派生类的对象,在使用完毕准备销毁时,如果基类的析构函数没有定义成虚函数,那 么编译器根据指 针类型就会认为当前对象的类型是基类,调用基类的析构函数,仅执行基类的析构,所以造成内存泄漏
  2. 构造函数不写成虚函数

    • 我们创建一个对象,是需要知道对象的完整信息的。特别是要知道对象的具体类型,而虚函数只需要知道函数接口,而不需要知道具体类型,所以构造函数不应该被定义为虚函数
    • 从目前编译器实现虚函数进行多态的方式来看,虚函数的调用是通过实例化之后对象 的虚函数表指针来找到虚函数的地址进行调用的,如果说构造函数是虚的,那么虚函数表 指针则是不存在的,无法找到对应的虚函数表来调用虚函数,那么这个调用实际上也是违 反了先实例化后调用的准则
  3. 构造函数和析构函数中不要调用虚函数

    • 举例来说,有一个动物的基类,基类中定义了一个动物本身行为的虚函数 action_type(), 在基类的构造函数中调用了这个虚函数

      派生类中重写了这个虚函数,我们期望着根据对象的真实类型不同,而调用各自实现的虚函 数,但实际上当我们创建一个派生类对象时,首先会创建派生类的基类部分,执行基类的构造 函数,此时,派生类的自身部分还没有被初始化,对于这种还没有初始化的东⻄,C++选择当 它们还不存在作为一种安全的方法

      派生类中重写了这个虚函数,我们期望着根据对象的真实类型不同,而调用各自实现的虚函 数,但实际上当我们创建一个派生类对象时,首先会创建派生类的基类部分,执行基类的构造 函数,此时,派生类的自身部分还没有被初始化,对于这种还没有初始化的东⻄,C++选择当 它们还不存在作为一种安全的方法

    • 在析构函数中也是同理,派生类执行了析构函数后,派生类的自身成员呈现未定义的状态,那 么在执行基类的析构函数中是不可能调用到派生类重写的方法的

  4. 哪些函数不能写成虚函数

    • 构造函数: 上述已说明
    • 内联函数: 内联函数表示在编译阶段进行函数体的替换操作,而虚函数意味着在运行期间进行 类型确定,所以内联函数不能是虚函数
    • 静态函数: 静态函数不属于对象属于类,静态成员函数没有this指针,因此静态函数设置为虚 函数没有任何意义
    • 友元函数: 友元函数不属于类的成员函数,不能被继承。对于没有继承特性的函数没有虚函数 的说法
    • 普通函数: 普通函数不属于类的成员函数,不具有继承特性,因此普通函数没有虚函数