C++中析构函数为何是虚函数

目录
  • 析构函数为什么是虚函数
  • 构造函数为什么不能出虚函数
  • 为什么构造函数和析构函数都不能调用虚函数
  • c++基类的析构函数为虚函数的原因
    • 原因
    • 例子
  • 总结

析构函数为什么是虚函数

虚构函数是虚函数的情况只需要在特定场景下出现即可,正常情况下不必要弄成虚函数。

如果基类的析构函数不是虚函数,在特定情况下会导致派生来无法被析构。

  • 情况1:用派生类类型指针绑定派生类实例,析构的时候,不管基类析构函数是不是虚函数,都会正常析构
  • 情况2:用基类类型指针绑定派生类实例,析构的时候,如果基类析构函数不是虚函数,则只会析构基类,不会析构派生类对象,从而造成内存泄漏。为什么???因为析构的时候如果没有虚函数的动态绑定功能,就只根据指针的类型来进行的,而不是根据指针绑定的对象来进行,所以只是调用了基类的析构函数;如果基类的析构函数是虚函数,则析构的时候就要根据指针绑定的对象来调用对应的析构函数了。

构造函数为什么不能出虚函数

因为类的虚函数表指针是在构造函数中初始化的,这时候如果构造函数本身是虚函数,又应该由谁来初始化它的虚函数指针呢,所以构造函数不能是虚函数。

为什么构造函数和析构函数都不能调用虚函数

    #include <iostream.h>

    using namespace std;

    class Base{
        public:
            Base(){
                cout << "Base::Base()\n";
                fun();
            }
            virtual ~Base(){
                cout << "Base::Base()\n";
                fun();
            }
            virtual void fun(){
                cout << "Base::fun() virtual\n";
            }
    };

    // 派生类
    class Derive: public Base{
        public:
            Derive(){
                cout << "Derive::Derive()\n";
                fun();
            }
            ~Derive(){
                cout << "Derive::Derive()\n";
                fun();
            }
            virtual void fun(){
                cout << "Derive::fun() virtual\n";
            }
    };

    int main()
    {
        Base *bd = new Derive();  // 基类Base的指针bd指向的是派生类Derive的对象
        delete bd;
        return 0;
    }

对于上述情况,基类指针指向派生类对象。构造时,先调用基类Base的构造函数,此时构函数中调用基类中的fun()函数,此时虚函数的动态绑定机制并没有会生效,这是因为此时的派生类还不存在。析构时,先析构派生类,派生类中的fun()函数调用的是自己的fun(),然后析构基类Base,基类析构函数中的fun()调用的是基类Base自己的fun()函数,这里虚函数的动态绑定机制也没有生效,因为此时派生类已经不存在了。

不要在构造函数中调用虚函数的原因:因为父类对象会在子类之前进行构造,此时子类部分的数据成员还未初始化, 因此调用子类的虚函数是不安全的,故而C++不会进行动态联编。

不要在析构函数中调用虚函数的原因:析构函数是用来销毁一个对象的,在销毁一个对象时,先调用子类的析构函数,然后再调用基类的析构函数。所以在调用基类的析构函数时,派生类对象的数据成员已经“销毁”,这个时再调用子类的虚函数已经没有意义了。

c++基类的析构函数为虚函数的原因

原因

在实现多态时, 当用基类指针操作派生类, 在析构时候防止只析构基类而不析构派生类。

例子

(1): 

#include<iostream>
  using namespace std;
  class Base{
  public:
     Base() {};
    ~Base() {cout << "Output from the destructor of class Base!" << endl;};
 
    void DoSomething() { cout << "Do something in class Base!" << endl; };
  };
 
  class Derived : public Base{
  public:
     Derived() {};
    ~Derived() { cout << "Output from the destructor of class Derived!" << endl; };
 
    void DoSomething() { cout << "Do something in class Derived!" << endl; };
  };
  int  main()
   { 
     Derived* p = new Derived;
     p->DoSomething();
     delete p;
     return 0;
  }

运行结果:

Do something in class Derived!

Output from the destructor of class Derived!

Output from the destructor of class Base!

代码中基类的析构函数不是虚函数,在main函数中用继承类的指针去操作继承类的成员,释放指针P的过程是:先释放继承类的资源,再释放基类资源。

(2):

  #include<iostream>
  using namespace std;
  class Base{
  public:
     Base() {};
    ~Base() {cout << "Output from the destructor of class Base!" << endl;};
 
    void DoSomething() { cout << "Do something in class Base!" << endl; };
  };
  class Derived : public Base{
  public:
     Derived() {};
    ~Derived() { cout << "Output from the destructor of class Derived!" << endl; };
 
    void DoSomething() { cout << "Do something in class Derived!" << endl; };
  };
  int  main(){ 
     Base* p = new Derived;
     p->DoSomething();
     delete p;
     return 0;
   }

运行结果:

Do something in class ClxBase!
Output from the destructor of class ClxBase!

代码中基类的析构函数同样不是虚函数,不同的是在main函数中用基类的指针去操作继承类的成员,释放指针P的过程是:只释放基类的资源,而没有调用继承类的析构函数。 调用DoSomething()函数执行的也是基类定义的函数。

一般情况下,这样的删除只能够删除基类对象,而不能删除子类对象,形成了删除一半形象,造成内存泄漏。

在公有继承中,基类对派生类及其对象的操作,只能影响到那些从基类继承下来的成员。如果想要用基类对非继承成员进行操作,则要把基类的这个函数定义为虚函数。 析构函数自然也应该如此:如果它想析构子类中的重新定义或新的成员及对象,当然也应该声明为虚的。

(3): 

#include<iostream>
  using namespace std;
  class Base{
  public:
     Base() {};
    virtual ~Base() {cout << "Output from the destructor of class Base!" << endl;};
 
    virtual void DoSomething() { cout << "Do something in class Base!" << endl; };
  };
  class Derived : public Base{
  public:
     Derived() {};
    ~Derived() { cout << "Output from the destructor of class Derived!" << endl; };
 
    void DoSomething() { cout << "Do something in class Derived!" << endl; };
  };
  int  main(){ 
     Base* p = new Derived;
     p->DoSomething();
     delete p;
     return 0;
   }

运行结果:

Do something in class ClxDerived!
Output from the destructor of class ClxDerived!
Output from the destructor of class ClxBase!

代码中基类的析构函数被定义为虚函数,在main函数中用基类的指针去操作继承类的成员,释放指针P的过程是:释放了继承类的资源,再调用基类的析构函数。调用DoSomething()函数执行的也是继承类定义的函数。

小结:基类指针可以指向派生类的对象(多态性),如果删除该指针delete p;就会调用该指针指向的派生类析构函数,而派生类的析构函数又自动调用基类的析构函数,这样整个派生类的对象完全被释放。

如果析构函数不被声明成虚函数,则编译器实施静态绑定,在删除基类指针时,只会调用基类的析构函数而不调用派生类析构函数,这样就会造成派生类对象析构不完全。所以,将析构函数声明为虚函数是十分必要的。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • C++踩坑实战之构造和析构函数

    目录 前言 构造函数 通过构造函数实现的类型转换 派生类的构造函数 析构函数 继承中的析构函数 应用 总结 前言 我是练习时长一年的 C++ 个人练习生,喜欢野指针.模板报错和未定义行为(undefined behavior).之前在写设计模式的『工厂模式』时,一脚踩到了构造.继承和 new 组合起来的坑,现在也有时间来整理一下了. 构造函数 众所周知:在创建对象时,防止有些成员没有被初始化导致不必要的错误,在创建对象的时候自动调用构造函数(无声明类型),完成成员的初始化.即: Class c

  • C++中构造函数与析构函数的详解及其作用介绍

    目录 构造函数 默认构造函数 有参构造函数 析构函数 析构函数例子 析构函数执行时机 局部对象 全局对象 构造函数 构造函数 (constructor) 是一种特殊的成员函数. 它会在每次创建类的新对象时执行. 构造函数的名称与类的名称是完全相同的, 并且不会返回任何类型. 构造函数可用于为某些成员变量设置初始值. 格式: Class::Class(); // 构造函数 默认构造函数 如果用户自己没有定义构造函数, C++ 系统会自动生成一个默认构造函数. 这个构造函数体是空的, 没有参数, 不

  • C++超详细讲解构造函数与析构函数的用法及实现

    目录 写在前面 构造函数和析构函数 语法 作用 代码实现 两大分类方式 三种调用方式 括号法 显示法 隐式转换法 正确调用拷贝构造函数 正常调用 值传递的方式给函数参数传值 值传递方式返回局部对象 构造函数的调用规则 总结 写在前面 上一节解决了类与对象封装的问题,这一节就是对象的初始化和清理的构造函数与析构函数的内容了:对象的初始化和清理也是两个非常重要的安全问题:一个对象或者变量没有初始状态,对其使用后果是未知,同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题:c++利用了构

  • C++深入讲解对象的销毁之析构函数

    目录 一.对象的销毁 二.析构函数 三.小结 一.对象的销毁 生活中的对象都是被初始化后才上市的 生活中的对象被销毁前会做一些清理工作 —股而言,需要销毁的对象都应该做清理 解决方案 为每个类都提供一个 public 的 free 函数 对象不再需要时立即调用 free 函数进行清理 如下: 存在的问题 free 只是一个普通的函数,必须显示的调用 对象销毁前没有做清理,很可能造成资源泄漏 C++ 编译器是否能够自动调用某个特殊的函数进行对象的清理? 二.析构函数 C++ 的类中可以定义一个特殊

  • C++浅析析构函数的特征

    目录 定义 特征 编译器生成的默认析构函数 定义 析构函数:与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的.而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作 特征 1. 析构函数名是在类名前加上字符 ~. 2. 无参数无返回值. 3. 一个类有且只有一个析构函数.若未显式定义,系统会自动生成默认的析构函数. 4. 对象生命周期结束时,C++编译系统系统自动调用析构函数 举一个例子,大家来看下面的代码 typedef int DataType; clas

  • C++超详细讲解析构函数

    目录 特性 析构函数处理自定义类型 编译器生成的默认析构函数 特性 析构函数是特殊的成员函数 特征如下: 析构函数名是~类名: 无参数无返回值: 一个类有且只有一个析构函数: 对象声明周期结束,编译器自动调用析构函数: class Stack { public: Stack(int capacity = 4) : _size(0), _capacity(capacity), _p(new int[_capacity]) { cout << "Stack(int capacity =

  • C++中析构函数为何是虚函数

    目录 析构函数为什么是虚函数 构造函数为什么不能出虚函数 为什么构造函数和析构函数都不能调用虚函数 c++基类的析构函数为虚函数的原因 原因 例子 总结 析构函数为什么是虚函数 虚构函数是虚函数的情况只需要在特定场景下出现即可,正常情况下不必要弄成虚函数. 如果基类的析构函数不是虚函数,在特定情况下会导致派生来无法被析构. 情况1:用派生类类型指针绑定派生类实例,析构的时候,不管基类析构函数是不是虚函数,都会正常析构 情况2:用基类类型指针绑定派生类实例,析构的时候,如果基类析构函数不是虚函数,

  • C++中为何推荐要把基类析构函数设置成虚函数

    目录 C++何推荐要把基类析构函数设置成虚函数 C++中析构函数为虚函数问题 1.析构函数是否定义为虚函数的区别 2.派生类指针操作派生类对象,基类析构函数不是虚函数 3.基类指针操作派生类对象,基类析构函数不是虚函数 4.基类指针操作派生类对象,基类析构函数是虚函数 5.基类析构函数定义为虚函数的情况 总结 C++何推荐要把基类析构函数设置成虚函数 在C++中常听老师讲要把基类析构函数声明成虚函数,这是因为要防止使用基类指针在调用派生类对象析构函数时,触发静态绑定,调用不到派生类的析构函数,导

  • 探讨C++中不能声明为虚函数的有哪些函数

    常见的不不能声明为虚函数的有:普通函数(非成员函数):静态成员函数:内联成员函数:构造函数:友元函数. 1.为什么C++不支持普通函数为虚函数? 普通函数(非成员函数)只能被overload,不能被override,声明为虚函数也没有什么意思,因此编译器会在编译时邦定函数. 多态的运行期行为体现在虚函数上,虚函数通过继承方式来体现出多态作用,顶层 函数不属于成员函数,是不能被继承的 2.为什么C++不支持构造函数为虚函数? 这个原因很简单,主要是从语义上考虑,所以不支持.因为构造函数本来就是为了

  • C++中的多态与虚函数的内部实现方法

    1.什么是多态 多态性可以简单概括为"一个接口,多种行为". 也就是说,向不同的对象发送同一个消息, 不同的对象在接收时会产生不同的行为(即方法).也就是说,每个对象可以用自己的方式去响应共同的消息.所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数.这是一种泛型技术,即用相同的代码实现不同的动作.这体现了面向对象编程的优越性. 多态分为两种: (1)编译时多态:主要通过函数的重载和模板来实现. (2)运行时多态:主要通过虚函数来实现. 2.几个相关概念 (1)覆盖.

  • 解析C++编程中virtual声明的虚函数以及单个继承

    虚函数 虚函数是应在派生类中重新定义的成员函数. 当使用指针或对基类的引用来引用派生的类对象时,可以为该对象调用虚函数并执行该函数的派生类版本. 虚函数确保为该对象调用正确的函数,这与用于进行函数调用的表达式无关. 假定基类包含声明为 virtual 的函数,并且派生类定义了相同的函数. 为派生类的对象调用派生类中的函数,即使它是使用指针或对基类的引用来调用的. 以下示例显示了一个基类,它提供了 PrintBalance 函数和两个派生类的实现 // deriv_VirtualFunctions

  • C++中继承与多态的基础虚函数类详解

    前言 本文主要给大家介绍了关于C++中继承与多态的基础虚函数类的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 虚函数类 继承中我们经常提到虚拟继承,现在我们来探究这种的虚函数,虚函数类的成员函数前面加virtual关键字,则这个成员函数称为虚函数,不要小看这个虚函数,他可以解决继承中许多棘手的问题,而对于多态那他更重要了,没有它就没有多态,所以这个知识点非常重要,以及后面介绍的虚函数表都极其重要,一定要认真的理解~ 现在开始概念虚函数就又引出一个概念,那就是重写(覆

  • C++中虚函数与纯虚函数的用法

    本文较为深入的分析了C++中虚函数与纯虚函数的用法,对于学习和掌握面向对象程序设计来说是至关重要的.具体内容如下: 首先,面向对象程序设计(object-oriented programming)的核心思想是数据抽象.继承.动态绑定.通过数据抽象,可以使类的接口与实现分离,使用继承,可以更容易地定义与其他类相似但不完全相同的新类,使用动态绑定,可以在一定程度上忽略相似类的区别,而以统一的方式使用它们的对象. 虚函数的作用是实现多态性(Polymorphism),多态性是将接口与实现进行分离,采用

  • 浅谈C++基类的析构函数为虚函数

    1.原因: 在实现多态时, 当用基类指针操作派生类, 在析构时候防止只析构基类而不析构派生类. 2.例子: (1). #include<iostream> using namespace std; class Base{ public: Base() {}; ~Base() {cout << "Output from the destructor of class Base!" << endl;}; void DoSomething() { cout

  • 详解C++编程中的虚函数

    我们知道,在同一类中是不能定义两个名字相同.参数个数和类型都相同的函数的,否则就是"重复定义".但是在类的继承层次结构中,在不同的层次中可以出现名字相同.参数个数和类型都相同而功能不同的函数. 人们提出这样的设想,能否用同一个调用形式,既能调用派生类又能调用基类的同名函数.在程序中不是通过不同的对象名去调用不同派生层次中的同名函数,而是通过指针调用它们.例如,用同一个语句"pt->display( );"可以调用不同派生层次中的display函数,只需在调用前

  • 浅谈C++中虚函数实现原理揭秘

    编译器到底做了什么实现的虚函数的晚绑定呢?我们来探个究竟. 编译器对每个包含虚函数的类创建一个表(称为V TA B L E).在V TA B L E中,编译器放置特定类的虚函数地址.在每个带有虚函数的类 中,编译器秘密地置一指针,称为v p o i n t e r(缩写为V P T R),指向这个对象的V TA B L E.通过基类指针做虚函数调 用时(也就是做多态调用时),编译器静态地插入取得这个V P T R,并在V TA B L E表中查找函数地址的代码,这样就能调用正确的函数使晚捆绑发生

随机推荐