精华内容
下载资源
问答
  • C++析构函数使用virtual的原因
  • 析构函数使用

    2019-10-04 02:24:09
    说到虚析构函数,我们先来看看析构函数的作用:在删除指向动态分配对象的指针时,需要运行析构函数在释放对象的内存之前清除对象。 在处理继承层次的对象时,指针的静态类型可能与被删除对象的动态类型不符,可能会...

    说到虚析构函数,我们先来看看析构函数的作用:在删除指向动态分配对象的指针时,需要运行析构函数在释放对象的内存之前清除对象。

    在处理继承层次的对象时,指针的静态类型可能与被删除对象的动态类型不符,可能会删除实际指向派生类对象的基类类型指针。

    怎么理解这句话呢,在有继承的时候,如果我们删除一个指向子类的基类类型的指针F,也就是说指针F的类型是基类,但这个指针指向子类。这个时候如果要删除这个指针,调用的应该是基类的析构函数。如下代码:

     1 //base.h
     2 
     3 #include <iostream>
     4 
     5 using namespace std;
     6 
     7 class A{
     8 public:
     9     A() {
    10         cout << "A" << endl;
    11     }
    12     ~A() {
    13         cout << "~A" << endl;
    14     }
    15 };
    16 
    17 class B: public A{
    18 public:
    19     B() {
    20         cout << "B" << endl;
    21     }
    22     ~B() {
    23         cout << "~B" << endl;
    24     }
    25 };
     1 //main.cpp
     2 
     3 #include"base.h" 
     4 #include<iostream>
     5 
     6 using namespace std;
     7 
     8 int main(int argc,char*argv[])
     9 { 
    10     A* te = new B;
    11     delete te;
    12     return 0;
    13 } 

    运行结果是:

    A
    B
    ~A
    请按任意键继续. . .

    但是实际上对象是B类型的,也就是说应该调用B的析构函数才对。

    我们来考虑一种情况,当类B中有一个新的函数,改函数开出了一个内存空间,如new 出了一个对象。那么在删除指针F的时候,我们调用的是基类的析构函数,但基类的析构函数没有释放类B中的那个函数开出的空间,就会造成内存泄漏。为了解决这个问题,我们必须另基类的析构函数为虚函数。

    如果基类的析构函数为虚函数,那么通过指针调用时,运行哪个析构函数将因指针所指的对象类型的不同而不同。

    如下:

     1 #include <iostream>
     2 
     3 using namespace std;
     4 
     5 class A{
     6 public:
     7     A() {
     8         cout << "A" << endl;
     9     }
    10     virtual ~A() {
    11         cout << "~A" << endl;
    12     }
    13 };
    14 
    15 class B: public A{
    16 public:
    17     B() {
    18         cout << "B" << endl;
    19     }
    20     ~B() {
    21         cout << "~B" << endl;
    22     }
    23 };

     

    运行结果:

    A
    B
    ~B
    ~A
    请按任意键继续. . .

    像其他虚函数一样,析构函数的虚函数性质都将继承。因此,如果层次中根类的析构函数为虚函数,那么派生类的析构函数也将是虚函数,无论派生类显式定义析构函数还是使用合成析构函数,派生类的析构函数都是虚函数。

    因此,为了程序的正常运行,就算析构函数没有工作要做,继承层次的根类也应该定义一个虚析构函数。

    转载于:https://www.cnblogs.com/xiezhw3/p/3545220.html

    展开全文
  • C++析构函数使用virtual的原因介绍  析构函数也可以是虚的,甚至是纯虚的。但是构造函数不能是虚的。例如: class A { public: virtual ~A()=0; // 纯虚析构函数 }; 类型需要虚析构函数的另外一个特征...

    C++析构函数使用virtual的原因介绍  

    析构函数也可以是虚的,甚至是纯虚的。但是构造函数不能是虚的。例如:

    class A { public: virtual ~A()=0; // 纯虚析构函数 };

    类型需要虚析构函数的另外一个特征是该类型具有指针成员或引用成员。如果有指针成员和引用成员,则该类型通常需要实现析构函数以及拷贝操作。通常,一个实现了析构函数的类型同时也需要实现拷贝构造函数与拷贝复制函数。

     

    作为一个经验法则:如果你有一个带有虚函数功能的类,则它需要一个虚析构函数,原因如下:

     1. 如果一个类有虚函数功能,它经常作为一个基类使用。

     2.如果它是一个基类,它的派生类经常使用new来分配。

     3.如果一个派生类对象使用new来分配,并且通过一个指向它的基类的指针来控制,那么它经常通过一个指向它的基类的指针来删除它(如果基类没有虚析构函数,结果将是不确定的,实际发生时,派生类的析构函数永远不会被调用)。基类有虚析构函数的话,最底层的派生类的析构函数最先被调用,然后各个基类的析构函数被调用。

     当一个类打算被用作其它类的基类时,它的析构函数必须是虚的。考虑下面的例子:
    

    class A { public: A() { ptra_ = new char[10];} ~A() { delete[] ptra_;} // 非虚析构函数 private: char * ptra_; }; class B: public A { public: B() { ptrb_ = new char[20];} ~B() { delete[] ptrb_;} private: char * ptrb_; }; void foo() { A * a = new B; delete a; }

      在这个例子中,程序也许不会象你想象的那样运行,在执行delete a的时候,实际上只有A::~A()被调用了,而B类的析构函数并没有被调用!造成内存的泄漏,这是否有点儿可怕?
      如果将上面A::~A()改为virtual,就可以保证B::~B()也在delete a的时候被调用了。因此基类的析构函数都必须是virtual的。
    
    展开全文
  • 构造函数 和 析构函数是每个类都有的东西,但是经常见到的东西未必就是我们深刻了解的,相反这些内容更有可能成为隐藏很深的不稳定因素。 构造函数: 构造函数初始化非static成员变量,然后再做一些其他自定义...

    前言:

    构造函数 和 析构函数是每个类都有的东西,但是经常见到的东西未必就是我们深刻了解的,相反这些内容更有可能成为隐藏很深的不稳定因素。

     

    构造函数:

    构造函数初始化非static成员变量,然后再做一些其他自定义动作。

    构造会对成员变量挨个构造,如果是内置类型则使用默认值,如果是类类型,则使用默认构造函数,如果某个成员变量没有默认构造函数,则编译是会报错。

     

    析构函数:

    析构函数销毁所有非static成员变量(自定义类类型的成员变量,内置类型会自动释放),其实就是调用成员变量的析构(如果有析构的话,内置类型就自动释放),然后做一些自定义动作。

    析构则挨个析构成员变量,如果类中保存了一个指针成员变量,那么析构时只会析构这个指针,而不会析构指针指向的内容。因此,在使用指针时,务必注意是否指向了new出来的内存,如果是,这部分内存有没有什么 地方会释放它,如果没有,那么请在析构函数中手动释放。

    上面提到了可能需要手动delete指针类型的成员变量,这个动作是有风险的,因为这个指针指向的内存可能别的实例也持有,这就可能存在重复释放相同内存的风险。这里可以使用智能指针来完成内存的释放。

    如果一个类需要自定义析构函数,那么根据经验判断,这个类肯定需要 自定义 拷贝构造 和 赋值构造。

    原因:

           某个类有指针类型的数据成员,指向new出来的内存。那么这个类必须要在析构函数中delete这个指针。那么,如果使用默认拷贝构造会怎么样,此时这个指针会被复制,此时就有多个指针指向相同的动态内存区,当其中一个实例在析构中释放动态内存区是,其他实例的指针将变为野指针。因此,需要自定义拷贝构造和拷贝赋值,在其中,将指针赋值换成创建新对象,然后再赋值给指针。   

     

    =default补充:

    只能对具有合成版本的函数使用 =default ,=default是要求编译器为我们生成默认版本的函数,如果这个函数都没有合成版本(由编译器生成),那么=default告诉编译器,编译器才不鸟你,直接报错。
     

    展开全文
  • 使用原则:类的构造函数不能是virtual函数,而类的析构函数可以且在类的继承中基类的析构函数通常为虚函数。 原因分析: 类的构造函数不能是虚函数:通俗地理解构造函数的作用就是为当前类创建对应的对象,如果为虚...

    1、virtual与构造、析构函数

    • 使用原则:类的构造函数不能是virtual函数,而类的析构函数可以且在类的继承中基类的析构函数通常必须为虚函数。
    • 基类的的析构函数不是虚函数的话,删除指针时,只有该类的内存被释放,派生类的没有,这样就内存泄漏了。
    • 原因分析:
      • 类的构造函数不能是虚函数:通俗地理解构造函数的作用就是为当前类创建对应的对象,如果为虚函数,则创建的不能为当前类创建对象,违背了构造函数的原则。
      • 类的析构函数可以是虚函数:理论上,由于类的析构函数为虚函数是合理的。而且,在继承层级的具体实现中,一般都是用父类的指针指向子类对象,那么如果基类的析构函数是虚函数,在delete 基类指针时,会调用子类的析构函数并释放该子类对象所占空间;而基类的析构函数不是虚函数,则并不会调用子类的析构函数,造成内存泄漏。下面为例子:
    • 代码实例:
    #include<iostream>
    using namespace std;
    class ClxBase
    {
    public:
    	ClxBase() {};
    	virtual ~ClxBase() {};
    	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() override{ cout << "Do something in class ClxDerived!" << endl; };
    };
    
    
    int main()
    {
    	ClxBase *pTest = new ClxDerived;
    	pTest->DoSomething();
    	delete pTest;
    
    	system("pause");
    	return 0;
    }
    
    • 运行结果
      在这里插入图片描述
      而基类的析构函数不是虚函数运行结果如下,可见子类的析构函数并没用调用!
      在这里插入图片描述
    • 实例代码注意
      • 一般用new关键字创建的对象一定要使用delete关键字进行释放。注意new 时会调用构造函数对创建的对象进行初始化,delete时会调用析构函数进行释放对象前的清理工作。注意,析构函数的作用并不是删除对象,而是在撤销对象占用的内存之前完成一些清理工作;同理构造函数也不是创建对象,而是对创建的对象进行一些初始化工作。

    2、内存泄漏

    • 既然上面内容涉及到了内存泄漏的知识则在这里总结下c++内存泄漏的几种情况。
    • 1、没有匹配地使用new、delete。因为使用new则是在堆上创建的内存,需要用户手动释放。(延申:非new形式定义的对象在进程虚拟地址空间的栈地址上存储,new申请的空间在堆上创建。两种创建位置主要区别:1.空间大小 2、是否由系统自动回收3、创建速度)
    • 2、使用delete释放指向对象数组的指针时没有加[]。主要这里的对象指的时自定义的类对象,简单的基本数据类型对象不加[]也可以。其原因是如果没有方括号,那么这个指针就被默认为只指向一个对象,对象数组中的其他对象的析构函数就不会被调用,结果造成了内存泄露;而基本类型系统记忆其空间大小,无需调用构造函数。
    • 3、基类的析构函数不是虚函数。(当基类指针指向子类对象时,如果基类的析构函数不是virtual,那么子类的析构函数将不会被调用,子类的资源没有正确是释放,因此造成内存泄露)
    • 4、缺少拷贝构造函数。如果没有定义拷贝构造函数,那么编译器就会调用默认的拷贝构造函数,会逐个成员拷贝的方式来复制数据成员,如果是以逐个成员拷贝的方式来复制指针被定义为将一个变量的地址赋给另一个变量。这种隐式的指针复制结果就是两个对象拥有指向同一个动态分配的内存空间的指针。当释放第一个对象的时候,它的析构函数就会释放与该对象有关的动态分配的内存空间。而释放第二个对象的时候,它的析构函数会释放相同的内存,这样是错误的。
    • 5、缺少赋值构造函数。原理与拷贝构造函数相同。
    • 6、对象指针数组的释放与对象数组的释放不同。(数组中存放的是指向对象的指针,不仅要释放每个对象的空间,还要释放每个指针的空间,delete []p只是释放了每个指针,但是并没有释放对象的空间,正确的做法,是通过一个循环,将每个对象释放了,然后再把指针释放了。)

    3、野指针出现情况

    • 1、定义指针没赋值NULL;
    • 2、指针越界访问
    • 3、delete ptr指针后,没有将NULL赋值给ptr。因为delete只是删除了ptr指向的内存空间,并不是删除了ptr指针。
    展开全文
  • 1、什么是虚函数和多态 虚函数是在类中被声明为virtual的成员函数,当编译器看到通过指针或引用调用此类函数时,对其执行晚绑定,也就是运行时多态,即通过指针(或引用)指向的类的类型信息来决定该函数是哪个类...
  • 我们知道,用C++开发的时候,用来做基类的类的析构函数一般都是虚函数。可是,为什么要这样做呢?下面用一个小例子来说明:  有下面的两个类: class ClxBase  {  public:   ClxBase() {}   virtual ~...
  • 这篇文章用于总结当析构函数是普通析构函数、虚析构函数、纯虚析构函数时,我们使用delete运算符删除一个指针对象时,析构函数会有什么情况发生; 普通析构函数 CBase是基类,CDerive是其子类,类源码代码如下: ...
  • 为什么标准C++建议将虚构函数定义成虚函数,下面就来探讨这个问题。#include <iostream> class Base { public: Base() {data = new char[64]; } ~Base(){delete [] data;} private: char *data; };...
  • 实现了一段定时器程序代码,其中使用了list来存储一个结构timerNode,并在list类中的析构函数实现了对该timerNode的一段操作,该操作是要将该list清空,但是不会释放内存 1.timerList类的部分实现: CTimerList::...
  • 这篇文章用于总结当析构函数是普通析构函数、虚析构函数、纯虚析构函数时,我们使用delete运算符删除一个指针对象时,析构函数会有什么情况发生;普通析构函数CBase是基类,CDerive是其子类,类源码代码如下:class ...
  • 如果一个类中有指针,且在使用的过程中动态的申请了内存,那么最好显示构造析构函数在销毁类之前,释放掉申请的内存空间,避免内存泄漏。 析构函数的执行顺序:1)派生类本身的析构函数体;2)对象成员析构函数;3)...
  • 在这个类的析构函数中,我使用 MSXML DOM 将内存中的数据写 回XML 配置文件,结果出错了。 在下面第 4 条语句出错: CoInitialize(NULL); try { 1、MSXML2::IXMLDOMDocumentPtr pDoc = NULL; 2、MSXML2:...
  •  因为在C++中,当一个派生类对象通过使用一个基类指针删除,而这个基类有一个非虚的析构函数,则结果是未定义的。运行时比较有代表性的后果是对象的派生部分不会被销毁。然而,基类部分很可能已被销毁,这导致了一...
  • 不论基类的析构函数是否为virtual的,派生类的对象在过期时都是先调用自己析构函数,然后再调用基类的析构函数。 另外: ...析构函数使用virtual的作用是“当使用基类指针指向派生类的时候,delete该指
  • #ifndef _SD_STRING_H_ #define _SD_STRING_H_ ...还有要注意的是, 友元函数的实现最好放到类定义的里面, 否则在编译时可能会出现不能够访问类私有成员函数的错误, 记得之前遇到过, 大概是编译器的问题...
  • 1、为什么要使用析构函数 我们知道析构函数是在对象生命周期结束时自动被调用,用来做一些清理工作(如释放句柄,释放堆内存等),防止出现内存泄漏。 那怎么还有虚析构函数呢? 使用析构函数的类一般是要...
  • 将可能会被继承的父类的析构函数设置为虚函数,可以保证当我们new一个子类,然后使用基类指针指向该子类对象,释放基类指针时可以释放掉子类的空间,防止内存泄漏。 C++默认的***析构函数不是虚函数是因为虚函数需要...
  • 尽管纯虚析构函数在标准C++中是合法的,但在使用时有一个额外的限制:必须 为纯虚析构函数提供一个函数体 纯虚析构函数和非纯虚析构函数之间唯一的不同之处在于纯虚析构函数使得 基类是抽象类,所以不能创建一个基类...
  • 如果析构函数使用delete pInstance;这时候析构函数会调用多次!无限循环,不清楚非单例模式是否会有这样的情况。 解决这个问题的方法就是使用deleteLater。 pInstance->deleteLater() Qt子窗体关闭时,不执行析...
  • 一、虚析构函数 类型需要虚析构函数的另外一个特征 是该类型具有指针成员或引用成员。如果有指针成员和引用成员,则该类型通常需要实现析构函数以及拷贝操作。 通常,一个实现了析构函数的类型同时也需要实现拷贝...
  • C++中基类的析构函数为什么要用virtual虚析构函数

    万次阅读 多人点赞 2018-08-11 15:53:07
    大家知道,析构函数是为了在对象不被使用之后释放它的资源,虚函数是为了实现多态。那么把析构函数声明为vitual有什么作用呢?直接的讲,C++中基类采用virtual虚析构函数是为了防止内存泄漏。具体地说,如果派生类中...
  • 也就是说,析构函数使用虚拟函数是常态情况,不使用是非常态。那么,为什么析构函数要声明为virtual呢? c++是一种静态编译语言,也就是说,绝大多数情况下,在编译期,各编译单元就完成了编译的运作,在实际运行时...
  • 1、构造函数是对象实体创建时调用的 ...使用析构函数的注意事项如下: (1)一个类中只可能定义一个析构函数。 (2)析构函数不能重载 (3)构造函数和析构函数不能使用return语句返回值,不用加上关键字void。
  • C++析构函数

    千次阅读 2019-09-18 09:02:42
    ①如果构造函数使用new来分配内存,则析构函数使用delete来释放内存; ②如果构造函数没有使用new,那么析构函数无任务,此时让编译器生成一个什么都不要做的隐式析构函数就行 特点: 如果程序员没有提供析构函数...
  • 析构函数析构函数的工作方式是:最底层的派生类(most derived class)的...因为在C++中,当一个派生类对象通过使用一个基类指针删除,而这个基类有一个非虚的析构函数,则结果是未定义的。运行时比较有代表...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 6,328
精华内容 2,531
关键字:

析构函数使用