精华内容
下载资源
问答
  •  虚析构函数  析构函数的工作方式是:底层的派生类(most derived class)的析构函数先被调用,然后调用每一个基类的析构函数。  因为在C++中,当一个派生类对象通过使用一个基类指针删除,而这个基类有一个非...
  • 本篇文章是对C++虚析构函数作用进行了详细的分析介绍,需要的朋友参考下
  • C++中的虚析构函数到底什么时候有用的,什么作用呢。 一.虚析构函数作用 总的来说虚析构函数是为了避免内存泄露,而且是当子类中会有指针成员变量时才会使用得到的。也就说虚析构函数使得在删除指向子类对象的基类...
  • //析构函数做成员函数 }; Base::~Base()//成员函数实现 { cout; } class Derived:public Base { public: Derived(); ~Derived(); private: int *p; }; Derived::Derived() { p=new int(0);//从堆上分配一个int型...
  • C++ 虚析构函数与析构函数的区别

    千次阅读 2018-10-09 11:34:14
    换句话说当加了virtual 后,就会先执行子类的析构函数,再执行基类的析构函数。 2、不执行析构函数,就可能会存在内存泄露。 很明显,不加virtual的情况下,子类的析构函数没有执行。 那什么情况下用virtual析构...

    先看下面代码:

    class Parent
    {
    public:
    	Parent();
    	~Parent();
    };
    
    Parent::Parent()
    {
    	cout << "基类构造...." << endl << endl;
    }
    
    Parent::~Parent()
    {
    	cout << "基类析构..." << endl  << endl;
    }
    
    
    class Child :public Parent
    {
    public:
    	Child();
    	~Child();
    };
    
    Child::Child()
    {
    	cout << "子类构造..." << endl << endl;
    }
    
    Child::~Child()
    {
    	cout << "子类析构..." << endl << endl;
    }
    
    int main()
    {
    	Parent* pObj = new Child();
    	delete pObj;
    }
    
    

    以上main函数执行结果为:

    基类构造....
    
    子类构造...
    
    基类析构...
    

    通过结果可以看出子类的析构函数没有调用。

    修改代码将上面的基类和子类的析构函数都改成一下:

    	virtual ~Parent();
    	virtual ~Child();
    

    执行结果如下:

    基类构造....
    
    子类构造...
    
    子类析构...
    
    基类析构...
    

    比较2次结构可以看出
    1、就是一个在析构的时候执行了子类的析构函数,一个在析构的时候没有执行子类的函数。
    换句话说当加了virtual 后,就会先执行子类的析构函数,再执行基类的析构函数。
    2、不执行析构函数,就可能会存在内存泄露。
    很明显,不加virtual的情况下,子类的析构函数没有执行。

    那什么情况下用virtual析构函数呢?
    个人理解是,C++的特性之一就是多态,当需要用一个基类的指针new子类的对象的时候,在这种情况下,构造的时候基类和子类都有,所以析构的时候,也应该都需要,所以要加上virtual。

    展开全文
  • 虚析构函数作用: 析构函数是可以声明为虚函数的,如果基类指针指向一个new生成的派生类对象,通过delete作用于基类指针删除派生类对象时,有...所以C++中的析构函数通常是虚析构函数虚析构函数的声明语法: .
    1. 虚析构函数的作用:
      析构函数是可以声明为虚函数的,如果基类指针指向一个new生成的派生类对象,通过delete作用于基类指针删除派生类对象时,有以下两种情况:
      • 如果基类析构函数不为虚析构函数,则只会调用基类的析构函数,而派生类的析构函数不会被调用。因此派生类对象中派生的那部分内存无法析构释放。
      • 如果基类析构函数为虚析构函数,则释放基类指针的时候会调用基类和派生类中的所有析构函数,派生类对象中所有的内存空间都将被释放,包括继承类的部分。
        所以C++中的析构函数通常是虚析构函数。
        虚析构函数的声明语法:
    virtual ~类名();
    

    示例程序:

    #include <iostream>
    #include <stdlib.h>
    #include <memory>
    using namespace std;
    
    class Person
    {
    public:
    	Person()
    	{
    		cout << "我是基类构造函数" << endl;
    	}
    	virtual ~Person()
    	{
    		cout << "我是基类析构函数" << endl;
    	}
    	void Display()
    	{
    		cout << "我是基类" << endl;
    	}
    };
    
    class Student : public Person
    {
    public:
    	Student()
    	{
    		cout << "我是派生类构造函数" << endl;
    	}
    	~Student()
    	{
    		cout << "我是派生类析构函数" << endl;
    	}
    	void Display() {
    		cout << "我是派生类" << endl;
    	}
    };
    
    int main()
    {
    	Person *p = new Student();
    	p->Display();
    	delete p;
    	system("pause");
    	return 0;
    }
    

    运行结果:

    我是基类构造函数
    我是派生类构造函数
    我是基类
    我是派生类析构函数
    我是基类析构函数
    

    如果Person类中的析构函数前把virtual关键字去掉,则运行结果为:

    我是基类构造函数
    我是派生类构造函数
    我是基类
    我是基类析构函数
    

    总结:当基类的析构函数不是虚析构函数时 ,使用delete释放对象时只释放基类没有释放派生类;

    展开全文
  • C++中,不能声明虚构造函数,但可以声明虚析构函数。多态性是指不同的对象对同一消息有不同的行为特性。虚函数作为运行时多态性的基础,主要是针对对象的,而构造函数是在对象产生之前运行的,因此虚构造函数是没有...
  • C++虚析构函数作用及其原理分析

    万次阅读 多人点赞 2017-03-15 00:13:04
    C++中的虚析构函数到底什么时候有用的,什么作用呢。 一.虚析构函数作用 总的来说虚析构函数是为了避免内存泄露,而且是当子类中会有指针成员变量时才会使用得到的。也就说虚析构函数使得在删除指向子类对象的...

    参考http://blog.csdn.net/xiamentingtao/article/details/55517203

    C++中的虚析构函数到底什么时候有用的,什么作用呢。
    一.虚析构函数的作用
    总的来说虚析构函数是为了避免内存泄露,而且是当子类中会有指针成员变量时才会使用得到的。也就说虚析构函数使得在删除指向子类对象的基类指针时可以调用子类的析构函数达到释放子类中堆内存的目的,而防止内存泄露的.

    我们知道,用C++开发的时候,用来做基类的类的析构函数一般都是虚函数。可是,为什么要这样做呢?下面用一个小例子来说明:

    #include<iostream>
    using namespace std;
    
    class ClxBase
    {
        public:
            ClxBase() {};
            virtual ~ClxBase() { cout<<"delete ClxBase"<<endl; };
    
            virtual void DoSomething() { cout << "Do something in class ClxBase!" << endl;  };
    
    };
    
    class ClxDerived : public ClxBase
    {
        public:
            ClxDerived() {};
            ~ClxDerived() { cout << "Output from the destructor of class ClxDerived!" << endl;  };
    
            void DoSomething() { cout << "Do something in class ClxDerived!" << endl;  };
    
    };
    
    int main(int argc, char const* argv[])
    {
         ClxBase *pTest = new ClxDerived;
         pTest->DoSomething();
         delete pTest;
        return 0;
    }

    这里写图片描述
    但是,如果把类ClxBase析构函数前的virtual去掉,那输出结果就是下面的样子了:
    这里写图片描述
    没有调动子类的析构函数
    也就是说,类ClxDerived的析构函数根本没有被调用!一般情况下类的析构函数里面都是释放内存资源,而析构函数不被调用的话就会造成内存泄漏。我想所有的C++程序员都知道这样的危险性。当然,如果在析构函数中做了其他工作的话,那你的所有努力也都是白费力气。
    所以,文章开头的那个问题的答案就是--这样做是为了当用一个基类的指针删除一个派生类的对象时,派生类的析构函数会被调用。
    当然,并不是要把所有类的析构函数都写成虚函数。因为当类里面有虚函数的时候,编译器会给类添加一个虚函数表,里面来存放虚函数指针,这样就会增加类的存储空间。所以,只有当一个类被用来作为基类的时候,才把析构函数写成虚函数。

    总结一下虚析构函数的作用:
    (1)如果父类的析构函数不加virtual关键字
    当父类的析构函数不声明成虚析构函数的时候,当子类继承父类,父类的指针指向子类时,delete掉父类的指针,只调动父类的析构函数,而不调动子类的析构函数。
    (2)如果父类的析构函数加virtual关键字
    当父类的析构函数声明成虚析构函数的时候,当子类继承父类,父类的指针指向子类时,delete掉父类的指针,先调动子类的析构函数,再调动父类的析构函数。
    二.虚析构函数的原理分析

    #include<iostream>
    using namespace std;
    
    class Base
    {
    public:
        Base(){cout<<"create Base"<<endl;}
        virtual ~Base(){cout<<"delete Base"<<endl;}
    };
    
    class Der : public Base
    {
    public:
        Der(){cout<<"create Der"<<endl;}
        ~Der(){cout<<"Delete Der"<<endl;}
    };
    int main(int argc, char const* argv[])
    {
        Base *b = new Der;
        delete b;
    
        return 0;
    }

    从创建讲起,用gdb调试你会发现,
    (1)先调用父类的构造函数,再调用子类的构造函数,

    这里有一个问题:父类的构造函数/析构函数与子类的构造函数/析构函数会形成多态,但是当父类的构造函数/析构函数即使被声明virtual,子类的构造/析构方法仍无法覆盖父类的构造方法和析构方法。这是由于父类的构造函数和析构函数是子类无法继承的,也就是说每一个类都有自己独有的构造函数和析构函数。

    (2)而由于父类的析构函数为虚函数,所以子类会在所有属性的前面形成虚表,而虚表内部存储的就是父类的虚函数

    即使子类也有虚函数,但是由于是单继承,所以也只有一张虚表,这在上一篇博客多态中讲到过。
    执行 Base *b = new Der;之后b的最终形态
    这里写图片描述

    (3)当delete父类的指针时,由于子类的析构函数与父类的析构函数构成多态,所以得先调动子类的析构函数;之所以再调动父类的析构函数,是因为delete的机制所引起的,delete 父类指针所指的空间,要调用父类的析构函数。
    所以结果就是这样
    这里写图片描述

    展开全文
  • 通常我们在写一个基类的时候,若基类成员变量需要动态申请内存空间或基类成员变量引用了系统资源时,需定义类的析构函数来明确在删除对象时需要释放的成员。 1. 析构函数 析构函数在析构时调用。若在堆上申请的内存...

    关键字:scalar deleting destructor、虚表

    通常我们在写一个基类的时候,若基类成员变量需要动态申请内存空间或基类成员变量引用了系统资源时,需定义类的析构函数来明确在删除对象时需要释放的成员。

    1. 析构函数

    析构函数在析构时调用。若在堆上申请的内存,则不需要调用delete释放内存,只需要调用析构函数。

    1.1 析构函数与delete

    在堆上申请的内存需要delete删除对象。在删除对象时既要执行析构又要释放内存。

    class A
    {
    public:
    	~A(){}
    };
    int main()
    {
    	A *a = new A;
    	delete a;
    }
    

    上面的代码,delete a的过程是什么样的?其实执行了两步操作:

    1. 调用析构函数~A()
    2. 调用全局的::operator delete回收内存

    我们直接看看汇编:
    在这里插入图片描述
    但是并没有看到调用析构函数的地方,而是调用了一个A::‘scalar deleting destructor’,看看这个函数:
    在这里插入图片描述
    先调用了A::~A(),然后调用了全局的operator delete。

    编译器生成了一个A::'scalar deleting destructor’函数,函数中把调用析构函数和释放内存封装在一起。所有调用delete时,实际是调用了A::'scalar deleting destructor’函数,而且这个函数是由编译器生成的。

    A::'scalar deleting destructor’函数是不是一定会生成?

    class A
    {
    public:
    	~A(){}
    };
    
    int main()
    {
    	A a;
    }
    

    上一段代码中,在栈上创建的A a对象,不需要delete,并没有看到A::'scalar deleting destructor’函数,不会生成这个函数。只有调用了delete才会生成A::'scalar deleting destructor’函数。

    问题:下面这段代码能编译通过吗?为什么?

    class A
    {
    public:
    	~A(){}
    	void operator delete(void *p);
    };
    
    int main()
    {
    	// 1. 这句能编译通过,未使用delete函数,所以即使没定义也不影响
    	A a;
    	// 2. 这句编译失败
    	A *a1 = new A;
    	delete a1;	// 生成了A::'scalar deleting destructor'函数,而该函数内调用了A::operator delete,delete函数没有实现报错
    }
    

    2. 虚析构函数

    通常我们在写父类时,若父类有析构函数,通常都会声明为虚析构函数。这是因为若父类的析构函数为非虚函数则在子类对象析构时不会被调用。只有父类析构函数为虚函数时,子类对象析构时才会调用子类析构函数后再调用父类的析构函数。

    听到这里,我们肯定会有一个很大的问题:虚析构函数从子类到父类的链式调用是如何实现的?因为我们知道,若子类重写了父类的虚函数,则不管子类对象的静态类型是父类还是子类都只会调用子类的函数。

    还是使用上面的代码,只是把析构函数改成虚函数:

    class A
    {
    public:
    	virtual ~A(){}
    };
    
    int main()
    {
    	A a;
    }
    

    我们看看class A的结构(32位编译器):
    在这里插入图片描述
    可以看到class A占4byte,其中只有一个虚表指针元素vfptr,在32位中指针占4byte。A的虚表中第一个元素是A::{dtor},看着像是A的析构函数,我们把这个函数在汇编中找出来:

    int main()
    {
    	A a;
    	int dtorAddr = *(int*)(*(int*)&a);
    }
    

    在这里插入图片描述
    dtorAddr值为7737774,转为16进制:0x7611AE,在汇编中找到这个位置:
    在这里插入图片描述
    其实是指向A::‘vector deleting destructor’,但是实际是调用了A::'scalar deleting destructor’函数,并不是A::~A()。
    由此可以知道,虚析构函数会在虚表中添加一条指向’scalar deleting destructor’函数的函数指针。

    但是上面的A a是在栈上申请的内存,所以在释放时只需要调用析构函数,并不需要释放堆内存,所以a析构时依然调用的是A::~A():
    在这里插入图片描述

    ecx寄存器用于传递this指针

    问题:下面的代码是否能编译通过:

    class A
    {
    public:
     virtual ~A(){}
     void operator delete(void *p);
    };
    
    int main()
    {
    	A a;
    }
    

    编译不通过,因为编译器会生成A::'scalar deleting destructor’函数,而此函数会调用A::operator delete,但是未定义,所以出错。

    2.1 多继承的虚析构函数

    通过上面的分析,我们已经知道了编译器会为虚析构函数在虚表中添加一个指向A::'scalar deleting destructor’函数的函数指针,用于delete删除对象。

    我们再分析一下下面的代码,看看多继承时,虚函数和delete是如何工作的:

    #include <stdio.h>
    
    class Animal {
    public:
      virtual void eat() = 0;
      virtual ~Animal() {
      	printf("Animal is dead\n");
      }
    
      void operator delete(void* p) {
        printf("Reclaiming Animal storage from %p\n", p);
        ::operator delete(p);
      }
    };
    
    class Sheep : public Animal {
    public:
      virtual void eat() {
        printf("Sheep eat ...\n");
      }
    
      virtual ~Sheep() {
        printf("Sheep is dead\n");
      }
    
      void operator delete(void* p) {
        printf("Reclaiming Sheep storage from %p\n", p);
        ::operator delete(p);
      }
    };
    
    int main(int argc, char** argv) {
      Animal* ap = new Sheep;
      ap->eat();
      delete ap;
      return 0;
    }
    

    执行代码,可以知道delete ap时的执行顺序:

    1. Sheep::~Sheep()
    2. Animal::~Animal()
    3. Sheep::operator delete(void *p)

    ap的静态类型是Animal,编译器是如何知道:

    1. 如何知道ap是Sheep类型,并找到~Sheep()
    2. 调用完~Sheep()又是如何找到 ~Animal()并调用的
    3. delete ap,为什么调用Sheep::operator delete,而不是Animal的删除函数

    要想知道,还是看汇编代码。具体就不详细写了。在delete ap时,其实是调用’scalar deleting destructor’函数,而这个函数是在虚表中,指向了Sheep::‘scalar deleting destructor’,所以调用delete ap就会调用Sheep::~Sheep()和Sheep::operator delete(void*p)。但是Animal:: ~Animal()又是怎么被调用的?其实是编译器在作怪,编译器在Sheep:: ~Sheep()函数之后又调用了Animal:: ~Animal():
    在这里插入图片描述
    编译器通过将父类的析构函数写入子类析构函数之后,实现了从子类到父类的链式析构。
    而且,只要父类的析构函数是虚析构函数,则子类的析构函数也是虚析构函数,子类生成的‘deleting destructor’函数会替换虚表中父类的‘deleting destructor’函数。

    还有一个知识点:operator new、operator delete都是隐式静态函数。

    展开全文
  • C++虚析构函数

    2018-07-09 00:16:19
  • 由于父类析构函数不是函数,因此编译器只能找到父类析构函数,而无法通过续表找到子类析构函数,因此上例会造成内存泄漏,子类中的数据成员没法得到释放。最重要的是这种行为并不会被编译器察觉 因此effective ...
  • C++虚析构函数和纯虚函数的作用

    千次阅读 多人点赞 2017-12-26 23:38:10
    虚析构函数为了能够正确的调用对象的析构函数,一般要求具有层次结构的顶级类定义其析构函数为虚函数。因为在delete一个抽象类指针时候,必须要通过虚函数找到真正的析构函数。class Base { public: Base(){} ...
  • 这篇文章用于总结当析构函数是普通析构函数、虚析构函数、纯虚析构函数时,我们使用delete运算符删除一个指针对象时,析构函数会有什么情况发生;普通析构函数CBase是基类,CDerive是其子类,类源码代码如下:class ...
  • 虚函数的主要作用是建立抽象模型,从而可以达到方便扩展系统的目的。纯虚函数是是一种特殊的虚函数,可以说是虚函数的子集,它可以不需要再基类中写函数实现,而虚函数不写函数实现就会报错。 为啥会有虚函数? ...
  • 虚继承 当一个基类被声明为虚基类后,即使它成为了多继承链路上的公共基类,最后的派生类中也只有它的一个备份。例如: class CBase { }; class CDerive1:virtual public CBase{ };...如果没有虚析构函数,父类指针d
  • c++ virtual 虚析构函数及虚函数的详细例子.rar
  • 析构函数作用是当对象生命期结束后,收回对象占用的资源,析构函数的特点是: 1、析构函数名是在类名前加以符号“~”。 2、析构函数没有参数、返回类型和修饰符。 3、一个类中至多有一个析构函数,如果程序员...
  • c++中,如果没有为一个类提供析构函数,那么编译器会为这个类提供默认的析构的函数。由于析构函数的功能和构造函数相反,因此和默认的构造函数类似,编译器也会提供无用的默认的析构函数,和非无用的析构函数。两者...
  • C++中的虚析构函数

    千次阅读 2019-05-27 15:21:46
    1.虚析构函数作用:为了避免内存泄漏,而且是当子类中会有指针成员变量时才会使用到。即虚析构函数使得在删除指向子类对象的基类指针时,可以调用子类的析构函数来实现释放子类中堆内存的目的,从而防止内存泄漏。...
  • 虚析构函数 为了能够正确的调用对象的析构函数,一般要求具有层次结构的顶级类定义其析构函数为虚函数。因为在delete一个抽象类指针时候,必须要通过虚函数找到真正的析构函数。 class Base { public: Base(){} ...
  • C++中的虚析构函数到底什么时候有用的,什么作用呢。 一.虚析构函数作用 总的来说虚析构函数是为了避免内存泄露,而且是当子类中会有指针成员变量时才会使用得到的。也就说虚析构函数使得在删除指向子类对象的...
  • C++析构函数函数

    千次阅读 多人点赞 2019-04-12 23:13:17
    1、析构函数是否定义为函数的区别 (1)析构函数定义为函数时:基类指针可以指向派生类的对象(多态性),如果删除该指针delete []p;就会调用该指针指向的派生类析构函数,而派生类的析构函数又自动调用基类的...
  • c++虚析构函数在虚函数表中吗?

    千次阅读 2018-03-25 21:57:32
    因为析构函数的名字不一样啊......(好吧,是我too young了)实验代码的继承关系如下:class fa{ public: fa(){} virtual ~fa(){cout &lt;&lt; "des fa" &lt;&lt; endl;} virtual show...
  • C++中基类的析构函数为什么要用virtual虚析构函数

    万次阅读 多人点赞 2018-08-11 15:53:07
    直接的讲,C++中基类采用virtual虚析构函数是为了防止内存泄漏。具体地说,如果派生类中申请了内存空间,并在其析构函数中对这些内存空间进行释放。假设基类中采用的是非虚析构函数,当删除基类指针指向的派生类对象...
  • 问题:C++虚拟函数的简单范例本程序通过VC++ 6.0编译与测试,两段程序分别演示了不使用虚析构函数和使用虚析构函数的结果,具体代码如下://未使用虚析构函数,程序目的:删除子类和父类的变量 #include &lt;...
  • c++虚析构函数的必要性

    千次阅读 2018-04-22 15:59:00
    我们知道,用C++开发的时候,用来做基类的类的析构函数一般都是函数。 可是,为什么要这样做呢?下面用一个小例子来说明: 1 #include<iostream> 2 using namespace std; 3 class Base 4 { ...
  • 一、C++ 内存泄露 C++本身没有GC机制,所以需要开发人员做好内存管理。一般情况,C++内存泄露主要原因为: ...下面根据三个场景来说明基类中virtual虚析构函数作用 1. 派生类指针指向派生类对象 ...
  • C++ 虚析构函数的用法

    2015-03-06 13:59:41
    C++虚析构函数作用 我们知道,用C++开发的时候,用来做基类的类的析构函数一般都是虚函数。可是,为什么要这样做呢?下面用一个小例子来说明:  有下面的两个类: [cpp] view plaincopy ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 43,925
精华内容 17,570
关键字:

c++虚析构函数的作用

c++ 订阅