精华内容
下载资源
问答
  • C++虚析构函数必要性
    2020-09-04 23:35:43

    上节我们讲到,构造函数不能是虚函数,因为派生类不能继承基类的构造函数,将构造函数声明为虚函数没有意义。

    这是原因之一,另外还有一个原因:C++ 中的构造函数用于在创建对象时进行初始化工作,在执行构造函数之前对象尚未创建完成,虚函数表尚不存在,也没有指向虚函数表的指针,所以此时无法查询虚函数表,也就不知道要调用哪一个构造函数。下节将会讲解虚函数表的概念。

    析构函数用于在销毁对象时进行清理工作,可以声明为虚函数,而且有时候必须要声明为虚函数。

    为了说明虚析构函数的必要性,请大家先看下面一个例子:

    #include <iostream>
    using namespace std;
    
    //基类
    class Base{
    public:
        Base();
        ~Base();
    protected:
        char *str;
    };
    Base::Base(){
        str = new char[100];
        cout<<"Base constructor"<<endl;
    }
    Base::~Base(){
        delete[] str;
        cout<<"Base destructor"<<endl;
    }
    
    //派生类
    class Derived: public Base{
    public:
        Derived();
        ~Derived();
    private:
        char *name;
    };
    Derived::Derived(){
        name = new char[100];
        cout<<"Derived constructor"<<endl;
    }
    Derived::~Derived(){
        delete[] name;
        cout<<"Derived de
    更多相关内容
  • C++中,不能声明构造函数,但可以声明虚析构函数。多态是指不同的对象对同一消息有不同的行为特性。函数作为运行时多态的基础,主要是针对对象的,而构造函数是在对象产生之前运行的,因此构造函数是没有...
  • 我们知道,用C++开发的时候,用来做基类的类的析构函数一般都是函数。 可是,为什么要这样做呢?下面用一个小例子来说明: 1#include<iostream> 2 using namespace std; 3 class Base 4 { 5 public:...

     我们知道,用C++开发的时候,用来做基类的类的析构函数一般都是虚函数。

    可是,为什么要这样做呢?下面用一个小例子来说明: 

     1#include<iostream>
     2 using namespace std;
     3 class Base
     4 {
     5 public:
     6     Base() {}
     7     virtual ~Base();
     8 };
     9 
    10 class Subclass :public Base
    11 {
    12 public:
    13     Subclass() {}
    14     ~Subclass();
    15 };
    16 Base::~Base()
    17 {
    18     cout << "Base destructor is called." << endl;
    19 }
    20 
    21 Subclass::~Subclass()
    22 {
    23     cout << "Subclass destructor is called." << endl;
    24 }
    25 
    26 int main()
    27 {
    28     Base *b = new Subclass;
    29     delete b;
    30     return 0;
    31 }

    输出结果:   

    Subclass destructor is called.
    Base destructor is called.

    这个很简单,非常好理解。
    但是,如果把类Base析构函数前的virtual去掉,那输出结果就是下面的样子了:

    Base destructor is called.

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

    展开全文
  • C++虚析构函数必要性 C++程序员经常会面对的问题就是内存泄漏,如果操作不当发生内存泄漏,那么将会是毁灭性的Bug。在继承里,大部分基类中的析构函数通常会声明为函数,这样会避免指针占用的堆内存得不到释放...

    C++程序员经常会面对的问题就是内存泄漏,如果操作不当发生内存泄漏,那么将会是毁灭性的Bug。在继承里,大部分基类中的析构函数通常会声明为虚函数,这样会避免指针占用的堆内存得不到释放,从而造成内存泄漏的情况。

    #include <iostream>
    
    using namespace std;
    
    class People {
    public:
        People(char *name_parameters, int age_parameters);
    
        People(const People &people);
    
        ~People();
    
        virtual void display();
    
        char *get_name();
    
        int get_age();
    
    private:
        char *name;
        int age;
    };
    
    People::People(char *name_parameters, int age_parameters) : name(name_parameters), age(age_parameters) {
        cout << "people class constructed is created" << endl;
    }
    
    People::People(const People &people) { cout << "people copy constructed is used" << endl; }
    
    People::~People() { cout << "people destructed is used" << endl; }
    
    int People::get_age() { return this->age; }
    
    char *People::get_name() { return this->name; }
    
    void People::display() { cout << "name:" << name << ",age:" << age << endl; }
    
    class Teacher : public People {
    public:
        Teacher(char *name, int age, int salary);
    
        ~Teacher();
    
        void display();
    
        int get_salary();
    
    private:
        int salary;
    };
    
    Teacher::Teacher(char *name, int age, int salary) : People(name, age), salary(salary) {
        cout << "Teacher class constructed is created" << endl;
    }
    
    Teacher::~Teacher() { cout << "Teacher destructed is used" << endl; }
    
    int Teacher::get_salary() { return this->salary; }
    
    void Teacher::display() {
        cout << "name2:" << get_name() << ",age:" << get_age()\
     << "salary:" << salary << endl;
    }
    
    int main() {
        People *ptr = new People("people", 20);
        ptr = new Teacher("child", 10, 1000);
        delete ptr;
        return 0;
    }
    
    输出:
    people class constructed is created
    people class constructed is created
    Teacher class constructed is created
    people destructed is used
    

    上述程序可以观察到,基类中没有使用到虚析构函数,导致delete调用基类析构函数在释放内存时,没有将派生类对象产生的堆内存释放掉,这样就容易造成内存泄漏。那么为什么没有调用派生类的析构函数呢?因为这里函数是非虚函数,通过指针访问非虚函数时,编译器会根据指针的类型来访问析构函数,现在指针的类型是People,那么在没有虚函数的情况下就只能调用People的析构函数。下面将基类的析构函数声明为虚析构函数。

    #include <iostream>
    
    using namespace std;
    
    class People {
    public:
        People(char *name_parameters, int age_parameters);
    
        People(const People &people);
    
        virtual ~People();
    
        virtual void display();
    
        char *get_name();
    
        int get_age();
    
    private:
        char *name;
        int age;
    };
    
    People::People(char *name_parameters, int age_parameters) : name(name_parameters), age(age_parameters) {
        cout << "people class constructed is created" << endl;
    }
    
    People::People(const People &people) { cout << "people copy constructed is used" << endl; }
    
    People::~People() { cout << "people destructed is used" << endl; }
    
    int People::get_age() { return this->age; }
    
    char *People::get_name() { return this->name; }
    
    void People::display() { cout << "name:" << name << ",age:" << age << endl; }
    
    class Teacher : public People {
    public:
        Teacher(char *name, int age, int salary);
    
        ~Teacher();
    
        void display();
    
        int get_salary();
    
    private:
        int salary;
    };
    
    Teacher::Teacher(char *name, int age, int salary) : People(name, age), salary(salary) {
        cout << "Teacher class constructed is created" << endl;
    }
    
    Teacher::~Teacher() { cout << "Teacher destructed is used" << endl; }
    
    int Teacher::get_salary() { return this->salary; }
    
    void Teacher::display() {
        cout << "name2:" << get_name() << ",age:" << get_age()\
     << "salary:" << salary << endl;
    }
    
    int main() {
        People *ptr = new People("people", 20);
        ptr = new Teacher("child", 10, 1000);
        delete ptr;
        return 0;
    }
    
    输出:
    people class constructed is created
    people class constructed is created
    Teacher class constructed is created
    Teacher destructed is used
    people destructed is used
    

    通过上述程序的修改,声明基类的虚析构函数之后,delete掉基类的指针,会调用派生类的析构函数和基类的析构函数释放内存。因为虚函数不会在意指针的类型,而是观察指针指向的对象,根据指针指向的对象来调用成员(成员变量和成员函数)。也就是说,指针指向哪个对象,就调用哪个对象的成员。指针ptr指向了派生类的对象,那么在释放内存时会优先调用派生类的析构函数,再根据delete机制调用基类的析构函数,这样就解决了内存泄漏的问题。

    构造函数不能是虚函数

    主要有两个原因:

    1. 派生类不能继承基类的构造函数,因此把基类的构造函数声明为虚函数没有意义,无法实现多态;

    2. c++中的构造函数用来在创建对象时进行初始化工作,在执行构造函数的时候,对象尚未创建完成,虚函数表这个时候还不存在, 也没有指向虚函数表的指针,所以此时还无法查询虚函数表。也就不知道调用哪一个构造函数。

    虚析构函数的原理分析

    1. 由于基类的析构函数为虚函数,所以派生类会在所有属性的前面形成虚表,而虚表内部存储的就是基类的虚函数。
    2. 当delete基类的指针时,由于派生类的析构函数与基类的析构函数构成多态,所以得先调动派生类的析构函数;之所以再调动基类的析构函数,是因为delete的机制所引起的,delete 基类指针所指的空间,要调用基类的析构函数。
    展开全文
  • C++虚函数全面解析函数的工作原理:虚析构函数C++中的静态联编和动态联编 学习过C++的都知道可以通过函数实现多态。在基类中定义一个函数,在派生类中可以重写这个函数,实现派生类自己的特性。 函数的...


    学习过C++的都知道可以通过虚函数实现多态。在基类中定义一个虚函数,在派生类中可以重写这个虚函数,实现派生类自己的特性。

    虚函数的工作原理:

    C++规定了函数名参数返回值,没有规定实现,可以根据需要自行实现内容。通常编译器处理虚函数的方法是给每个对象添加一个隐藏成员。该成员保存了一个指向函数地址的数组指针,这个数组指针也就是虚函数表。虚函数表中保存了对象中所有虚函数的地址(包括继承的基类的虚函数地址),如果派生类多重继承就会存在多个虚函数表,派生类本身的虚函数表会出现在继承顺序第一个基类后面,下面举例演示一下:

    class a{
    public:
    	virtual void base_a1();
    	virtual void base_a2();
    ]
    class b{
    public:
    	virtual void base_b1();
    	virtual void base_b2();
    ]
    class c{
    public:
    	virtual void base_c1();
    	virtual void base_c2();
    ]
    class d:public a,public b,public c{
    	virtual void derive_d1();
    	virtual void derive_d2();
    }
    int main(){
    	d derive_d;
    }
    

    上面代码中定义derive_d的虚函数表结构如下图:
    在这里插入图片描述
    在对象所占内存的开始位置存在隐藏成员指针a、指针b、指针c,他们分别指向对应的虚函数表,对象自己的虚函数表在第一个继承的基类的后面。

    class d:public a,public b,public c{
    	void base_b2();
    	virtual void derive_d1();
    	virtual void derive_d2();
    }
    

    如果派生类重写了基类的函数(方法),那么在上图base_b2处的地址将变成指向派生类实现base_b2方法的地址。此时用派生类的对象在调用base_b2方法时调用的就是派生类重写的base_b2方法。

    虚析构函数:

    为什么基类的析构函数必须声明称虚函数呢?大家可能都知道,基类的析构函数不声明成虚函数时在释放时候有可能会导致子类的析构函数调用不到的情况。

    class Base{
    public:
        Base()
        {
            qDebug()<<"base";
        }
        ~Base()
        {
            qDebug()<<"~base";
        }
    };
    class Derive:public Base{
    public:
        Derive()
        {
            qDebug()<<"Derive";
        }
        ~Derive()
        {
            qDebug()<<"~Derive";
        }
    };
    int main(){
        Base *p=new Derive();
        delete(p);
    }
    输出:
    base
    Derive
    ~base
    

    上面代码执行情况就会先调用基类的构造函数然后调用派生类的构造函数,然后调用基类的析构函数。这就导致了内存泄露,为派生类分配了内存但是并没有释放。如果main函数像下面这样实现就不会出现这种情况:

    int main(){
    	Derive *p=new Derive;
    	delete(p);
    }
    输出:
    base
    Derive
    ~Derive
    ~base
    

    因为派生类的析构函数会调用基类的析构函数。虽然这种方法可以避免内存泄露,但是有时候某种情况下必须定义父类指针去指向一个子类的对象所以还是存在风险。只要将Base类如下声明就可以完全避免这个问题。

    class Base{
    public:
    	virtual ~Base();
    }
    输出:
    base
    Derive
    ~Derive
    ~base
    

    这样无论你是定义子类指针去指向一个子类对象还是用父类定义指针去指向子类对象都不会有内存泄露的问题。
    到这里可能大家都知道,但是作者在这里提出一个疑问,为什么父类声明成虚构造函数就能在delete时候既调用到子类的析构函数又能调用到父类的析构函数呢?这就不得不提一下C++中的静态联编和动态联编了。

    C++中的静态联编和动态联编

    在提到动态和静态的时候我不禁想起了C语言中的动态库和静态库,其实动态联编和静态联编的区别与动态库和静态库的区别十分相似。
    程序在调用函数时候调用哪个函数,执行哪段代码是由编译器负责的,在C语言中每个函数名不同调用起来就十分简单,但是C++中可以重载的缘故,编译器必须确定什么时候执行哪个函数调用哪段代码,在编译过程中可以确定调用哪段代码的被称为静态联编(早期联编)。由于C++中存在虚函数,使用哪个函数调用哪段代码在编译时并不能确定就像之前例子中说的定义父类的指针去指向一个子类的对象,这时编译器无法做出正确的判断,不知道是该调用父类的析构函数还是子类的析构函数。编译器必须在程序运行时调用正确的虚函数代码,这就被称为动态联编。动态联编和虚函数是息息相关的(虚函数采用动态联编非虚函数采用静态联编)。动态联编效率会低于静态联编,所以不要声明没有必要的虚函数,这样会导致效率降低。
    介绍完静态联编和动态联编,我们回到上面的问题:

    为什么父类声明成虚构造函数就能在delete时候既调用到子类的析构函数又能调用到父类的析构函数呢?

    因为在定义为非虚析构函数时会采用静态联编,调用析构函数时因为是静态联编,在编译时会按照指针定义的类型也就是父类的类型,所以在调用哪个析构函数做选择时会选择父类的析构函数;但是在将基类的析构函数定义为虚析构函数后,就会采用动态联编,调用析构函数时会按照指针实际所指向的类型,也就是子类的类型,调用的自然就是子类的析构函数。上面说到过子类的虚构函数会调用父类的析构函数,所以父类子类的析构函数都被调用到了。

    注意:

    1. 内联函数不能是虚函数,因为内联函数是在编译阶段展开的,而虚函数是在运行时动态调用的,编译时无法展开。
    2. 构造函数不能是虚函数,因为构造函数是在创建对象时候调用的,在创建派生类对象时会先调用基类的构造函数再调用派生类的构造函数。构造函数声明成虚函数是毫无意义的。
    3. 静态成员函数不能是虚函数,静态函数相当于普通函数和类、实例并没有关系所以也不存在多态,而虚函数是一种特殊的成员函数用来实现运行时多态的。所以静态成员函数声明成虚函数毫无意义。

    以上内容均是作者查阅资料加自己理解,如有疑问,望读者不吝赐教,谢谢!
    参考文献《C++ primer plus》(第六版)中文版

    展开全文
  • 由于类的多态,基类指针可以指向派生类的对象,如果删除该基类的指针,就会调用该指针指向的派生类析构函数,而派生类的析构函数又自动调用基类的析构函数,这样整个派生类的对象完全被释放。 如果析构函数不被...
  • C++中的虚析构函数到底什么时候有用的,什么作用呢。一.虚析构函数的作用 总的来说虚析构函数是为了避免内存泄露,而且是当子类中会有指针成员变量时才会使用得到的。也就说虚析构函数使得在删除指向子类对象的基类...
  • 基类析构函数是否为影响派生类析构函数的调用 class Base //基类 { public: ~Base(); //此处将基类析构函数设置为常规析构 }; Base::~Base() //基类析构函数 { cout << "Base Destruct" << ...
  • C++的类中,构造函数用于初始化对象及相关操作,构造函数是不能声明为函数的,因为在执行构造函数前...下面通过一个例子来说明下基类析构函数声明为函数的必要性。#include using namespace std;class base {p...
  • C++虚析构函数

    2020-02-14 08:14:11
    一句话总结:通常来说,如果基类中存在一个指向动态分配内存的成员变量,并且基类的析构函数中定义了释放该动态分配内存的代码,则应该将基类的析构函数声明为函数。不然会造成内存泄漏。 在类中,构造函数用于...
  • 如果一个类中定义了函数,那=析构函数也应说明未函数。        delete运算符和析构函数一起工作,当使用delete删除一个对象时,delete隐藏着对析构函数的一次调用。  &...
  • 为什么C++析构函数一般写成函数 由于类的多态,基类指针可以指向派生类的对象,如果删除该基类的指针,就会调用该指针指向的派生类析构函数,而派生类的析构函数又自动调用基类的析构函数,这样整个派生类的...
  • c++5.6虚析构函数

    2021-04-17 11:09:04
    一个类中只允许一个虚析构函数析构函数无参数) 虚析构函数的定义和使用 //虚析构函数 #include <iostream> using namespace std; class Base { public: Base( ){} virtual ~Base(){cout << "Base ...
  • C++析构函数

    2020-06-09 16:37:53
    下面记录一下一个小示例,目的是说明虚析构函数必要性。若析构函数不是函数,会发生什么,vs2010下创建控制台输出程序,下面看代码,以及运行实例; classObject.cpp #include "stdafx.h" #include <iostream...
  • 虚析构函数

    2022-01-22 19:14:13
    因为不作基类的情况下,这个类就没有必要定义其他函数,采用虚析构函数会为类增加一个函数表指针的长度,使得类对象占用的空间增加,还有可能降低其可移植。如下代码所示: #include "stdafx.h" class Base...
  • 问题:C++虚拟函数的简单范例本程序通过VC++ 6.0编译与测试,两段程序分别演示了不使用虚析构函数和使用虚析构函数的结果,具体代码如下://未使用虚析构函数,程序目的:删除子类和父类的变量 #include &lt;...
  • 详解析构函数出现的必要性输入输出需要内存!传统C语言应对策略申请动态内存的好处析构函数闪亮登场什么是析构函数析构函数的好处:wuli康康的参考资料输入输出需要内存!同学你好,你已经学了用scanf,cin指令...
  • 如果这是必要的,那么为什么C++不把虚析构函数直接作为默认值? 答案: 编译器总是根据类型来调用类成员函数。但是一个派生类的指针可以安全地转化为一个基类的指针。这样删除一个基类的指针的时候,C++不管这个指针...
  • 一旦一个类包含虚函数,它就应该包含一个析构器。如果一个类不用作基类或者不需具有多态,便不应该为它声明析构器。 1、原因:  在实现多态时, 当用基类指针操作派生类, 在析构时候防止只...
  • 这个要提一下,如果记不住就记住:如果不做虚析构函数,会有内存泄漏 解释 定义一个基类的指针p,在delete p时,如果基类的析构函数函数,这时只会看p所赋值的对象,如果p赋值的对象是派生类的对象,就会调用...
  • 今天我们来谈一谈面试 C++ 工程师时经常被谈到的一个问题:为什么析构函数必须是函数?为什么默认的析构函数不是函数? 首先,我们看一下百度百科对函数是怎么定义的: 在某基类中声明为 virtual并在一个或多个...
  • 基类指针可以指向派生类的对象(多态),如果删除该指针delete []p;就会调用该指针指向的派生类析构函数,而派生类的析构函数又自动调用基类的...所以,将析构函数声明为函数是十分必要的。 为什么基类的析构函...
  • c++虚析构函数必要性 构造函数不能是函数,主要有两个原因: 1、 派生类不能继承基类的构造函数,因此把基类的构造函数声明为函数没有意义,无法实现多态; 2、 c++中的构造函数用来在创建对象时进行...
  • C++:什么情况下需要将析构函数定义为函数。
  • 关于为什么要将析构函数定义成函数,为了加深印象,写了个小测试代码对比了一下: #include &lt;iostream&gt; using namespace std; class Base{ public:  Base()  {  cout &lt;&lt; "...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 19,333
精华内容 7,733
关键字:

c++虚析构函数的必要性

c++ 订阅