• c++多态


    一、多态是如何体现的?

    主要是通过虚函数和运行时类型识别机制。

    1、虚函数表:每个包含至少一个虚函数的类都有一个虚函数表。这个表包含了该类及其所有基类的虚函数的地址。当创建派生类对象时,该对象的内存布局会包含一个指向其虚函数表的指针(vfptr)。注:基类有自己的vfptr,派生类连基类的vfptr一起继承。

    2、动态绑定:当通过基类指针或引用调用虚函数时,编译器会在运行时通过vptr查找虚函数表,并确定要调用的实际函数(这取决于指针或引用所指向的对象的实际类型)。

    3、多态性:由于动态绑定,基类指针或引用可以指向基类或派生类的对象,并调用相应的虚函数。

    二、虚函数的底层实现

    虚函数的底层是通过虚函数表实现的。当类中定义了虚函数之后,就会在对象的存储开始位置,多一个虚函数指针,该虚函数指针指向一张虚函数表,虚函数表中存储的是虚函数入口地址。

    三、虚函数的限制

    1、构造函数不能设为虚函数

    构造函数的作用是创建对象,完成数据的初始化,而虚函数机制被激活的条件之一就是要先创建对象,有了对象才能表现出动态多态。如果将构造函数设为虚函数,此时构造还未执行完,对象还没创建出来,存在矛盾。

    2、静态成员函数不能设为虚函数

    虚函数的实际调用:this->vfptr->vtable->virtual function,但是静态成员函数没有this指针,所以无法访问到vfptr。

    3、inline函数不能设为虚函数

    因为inline函数在编译期间完成替换,而在编译期间无法展现动态多态机制,所以效果是冲突的,如果同时存在,inline失效。

    4、普通函数不能设为虚函数

    虚函数要解决的是对象多态的问题,与普通函数无关。

    四、重载、隐藏、覆盖的区分

    重载:发生在同一个类中,当函数名称相同时,函数参数类型、顺序、个数任一不同。. 

    隐藏:发生在基类派生类之间,函数名称相同时,就构成隐藏(参数不同也能构成隐藏)。它允许子类隐藏父类中的同名函数。

    列如:

    1. #include
    2. using std::cout;
    3. using std::endl;
    4. class A
    5. {
    6. public:
    7. void print()
    8. {
    9. cout << "A" << endl;
    10. }
    11. };
    12. class B
    13. : public A
    14. {
    15. public:
    16. void print(int x)
    17. {
    18. cout << "B" << endl;
    19. }
    20. };
    21. int main()
    22. {
    23. B b;
    24. b.print();
    25. return 0;
    26. }

     运行结果:

    在例子中,B类中的print(int x) 隐藏了A类中的print()。如果使用B类型的对象调用print(),编译器会报错,因为没有找到匹配的函数。

    覆盖:发生在基类和派生类之间,基类和派生类中同时定义相同的虚函数,覆盖的是虚函数表的入口地址,并不是覆盖函数本身。

    1. #include
    2. using std::cout;
    3. using std::endl;
    4. class A
    5. {
    6. public:
    7. virtual void print() const
    8. {
    9. cout << "A" << endl;
    10. }
    11. };
    12. class B
    13. : public A
    14. {
    15. public:
    16. void print() const override
    17. {
    18. cout << "B" << endl;
    19. }
    20. };
    21. int main()
    22. {
    23. B b;
    24. A *P = &b;
    25. P->print();
    26. return 0;
    27. }

    运行结果: 

     

    五、析构函数设为虚函数

     一、如果基类的析构函数不是虚函数,那么通过基类指针或引用调用析构函数时只会调用基类的析构函数,而不会调用派生类的析构函数。导致派生类部分没有被正确清理。为了避免这种情况,我,我们通常在基类中将析构函数声明为虚函数。

    1. #include
    2. using std::cout;
    3. using std::endl;
    4. class Base
    5. {
    6. public:
    7. Base()
    8. : _base(new int(10))
    9. {
    10. cout << "Base() " << endl;
    11. }
    12. virtual void display() const
    13. {
    14. cout << "*_base:" << *_base << endl;
    15. }
    16. virtual ~Base()
    17. {
    18. if (_base)
    19. {
    20. delete _base;
    21. _base = nullptr;
    22. }
    23. cout << "~Base()" << endl;
    24. }
    25. private:
    26. int *_base;
    27. };
    28. class Derived
    29. : public Base
    30. {
    31. public:
    32. Derived()
    33. : Base(), _derived(new int(20))
    34. {
    35. cout << "Derived()" << endl;
    36. }
    37. virtual void display() const override
    38. {
    39. cout << "*_derived:" << *_derived << endl;
    40. }
    41. ~Derived()
    42. {
    43. if (_derived)
    44. {
    45. delete _derived;
    46. _derived = nullptr;
    47. }
    48. cout << "~Derived()" << endl;
    49. }
    50. private:
    51. int *_derived;
    52. };
    53. int main()
    54. {
    55. Base *pbase = new Derived();
    56. pbase->display();
    57. delete pbase;
    58. return 0;
    59. }

    运行结果:

     

    当使用基类指针指向派生类对象并通过 delete 操作符删除该基类指针指向的对象时,如果基类的析构函数是虚函数,那么析构函数的多态性就会被触发。

    在delete ptr;这一行会发生以下事情:

    1、编译器首先会检查 ptr 所指向的对象的实际类型(在这种情况下是Derived类型)。

    2、由于 Base 类的析构函数是虚函数,编译器会查找该对象的虚函数表(Vtable),以确定应该调用哪个析构函数。

    3、编译器使用虚函数表找到Derived 类的析构函数,并首先调用它。

    4、Derived 类的析构函数执行完毕后,会自动调用基类Base的析构函数。

    这样,即使是通过基类指针ptr删除对象,也能确保Derived 类的析构函数被正确调用,从而避免了资源泄露等问题。

    注:析构函数顺序:派生类析构函数的执行顺序与构造函数相反。首先调用派生类的析构函数,然后按照基类在继承列表中的顺序(如果有多个基类)或按照基类声明的顺序(如果有单个基类)调用基类的析构函数。这确保了在对象销毁时,首先清理派生类特有的资源,然后清理基类共有的资源。

  • 相关阅读:
    解决typescript报错:不能将类型xxx分配给类型xxx
    关于 mmcv 及其衍生库配置中_delete_参数的问题
    ATtiny88初体验(二):呼吸灯
    Chapter7: SpringBoot与数据访问
    Java向BlockingQueue添加元素耗时长
    Spark Streaming_第七章笔记
    uniapp如何实现关闭前面指定数目页面
    LLM大模型4位量化实战【GPTQ】
    Object.defineProperty() 详解
    JavaSE_day12【异常】
  • 原文地址:https://blog.csdn.net/m0_55138981/article/details/140050558