C++构造函数、析构函数、虚函数之间的关系

1. 如果我们定义了一个构造函数,编译器就不会再为我们生成默认构造函数了。
2. 编译器生成的析构函数是非虚的,除非是一个子类,其父类有个虚析构,此时的函数虚特性来自父类。
3. 有虚函数的类,几乎可以确定要有个虚析构函数。
4. 如果一个类不可能是基类就不要申明析构函数为虚函数,虚函数是要耗费空间的。
5. 析构函数的异常退出会导致析构不完全,从而有内存泄露。最好是提供一个管理类,在管理类中提供一个方法来析构,调用者再根据这个方法的结果决定下一步的操作。
6. 在构造函数不要调用虚函数。在基类构造的时候,虚函数是非虚,不会走到派生类中,既是采用的静态绑定。显然的是:当我们构造一个子类的对象时,先调用基类的构造函数,构造子类中基类部分,子类还没有构造,还没有初始化,如果在基类的构造中调用虚函数,如果可以的话就是调用一个还没有被初始化的对象,那是很危险的,所以C++中是不可以在构造父类对象部分的时候调用子类的虚函数实现。但是不是说你不可以那么写程序,你这么写,编译器也不会报错。只是你如果这么写的话编译器不会给你调用子类的实现,而是还是调用基类的实现。
7.
在析构函数中也不要调用虚函数。在析构的时候会首先调用子类的析构函数,析构掉对象中的子类部分,然后在调用基类的析构函数析构基类部分,如果在基类的析构函数里面调用虚函数,会导致其调用已经析构了的子类对象里面的函数,这是非常危险的。
8. 记得在写派生类的拷贝函数时,调用基类的拷贝函数拷贝基类的部分,不能忘记了。

 
 

1、为什么构造函数不能是虚函数?

因为:从使用上来说,虚函数是通过基类指针或引用来调用派生类的成员的,则在调用之前,对象必须存在,而构造函数是为了创建对象的。

2、为什么在派生类中的析构函数常常为虚析构函数

注意,默认不是析构函数

一句话,是为了避免内存泄露

如果不考虑虚函数的状况,给出一个基类和派生类,如果调用派生类的析构函数时,肯定会引发调用基类的析构函数,这和析构函数是不是虚函数没关系。

现在考虑虚函数的问题,由于使用虚函数使我们可以定义一个基类指针或引用可以直接对派生类进行操作,这就存在两种情况:

如果,不把基类的析构函数设置为虚函数,则在删除对象时,如果直接删除基类指针,系统就只能调用基类析构函数,而不会调用派生类构造函数。这就会导致内存泄露。

如果,把基类的析构函数设置为虚函数,则在删除对象时,直接删除基类指针,系统会调用派生类析构函数,之后此派生类析构函数会引发系统自动调用自己的基类,这就不会导致内存泄露。

所以,在写一个类时,尽量将其析构函数设置为虚函数,但析构函数默认不是虚函数。

举例一:通过派生类指针删除派生类对象的情况

 #include<iostream>
using namespace std; class Base
{
public:
~Base()
{
cout<<" Base 的析构函数"<<endl;
} }; class Derive : public Base
{
public:
~Derive()
{
cout<<" Derive 的析构函数"<<endl;
} }; void main()
{
Derive* p = new Derive();
delete p;
system("pause");
}

结果:

分析:即调用了基类的析构函数,又调用了派生类的析构函数

说明:

(1)p是指向派生类Derive的指针,删除p时,会自动调用Derive的析构函数,同时在之后,有继续向上调用基类Base的析构函数。

(2)这个过程和虚函数没有关系,只要调用派生类的析构函数,就自动回调用其祖先的析构函数。

举例二:通过基类指针删除派生类对象时 且 没有把基类的析构函数设置为虚函数的情况

 #include<iostream>
using namespace std; class Base
{
public:
~Base()
{
cout<<" Base 的析构函数"<<endl;
} }; class Derive : public Base
{
public:
~Derive()
{
cout<<" Derive 的析构函数"<<endl;
} }; void main()
{
Base* p = new Derive();
delete p;
system("pause");
}

结果:

分析:只调用了基类的析构函数,没调用了派生类的析构函数

说明:

(1)由于p是指向基类Base的指针,而且其Base的析构函数也不是虚函数,在删除p时,会直接调用Base的析构函数,而不会调用自己实际指向Derive的析构函数(这是用来和后面对比的),如果在Derive中的析构函数中有内存的释放,就会造成内存泄露。

举例三:通过基类指针删除派生类对象时 且 把基类的析构函数设置为虚函数的情况

 #include<iostream>
using namespace std; class Base
{
public:
virtual ~Base()
{
cout<<" Base 的析构函数"<<endl;
} }; class Derive : public Base
{
public:
~Derive()
{
cout<<" Derive 的析构函数"<<endl;
} }; void main()
{
Base* p = new Derive();
delete p;
system("pause");
}

结果

分析

(1)由于p是指向派生类Base的指针,而且其析构函数是虚函数,由于虚函数的性质,在删除p时,会直接调用Derive的析构函数

(2)在调用Derive的析构函数后,会引发Derive的基类Base的析构函数的调用。这和其是否是虚函数没关系,只是析构函数自己的功能。

总结,在有派生存在的类集合中,基类的析构函数尽量设置为虚函数,而且常常为虚函数,但默认不是虚函数,而且不需要一定设置为虚函数。

注意:如果基类的析构函数设置为虚函数,那么所有的派生类也默认为虚析构函数,即使没有带关键字Virtual。

在一个基类中,析构函数设置为虚析构函数的原因是什么?

主要是因为:

(1)需要使用 指向基类指针对派生类进行操作时,才有可能会导致内存泄露。如果不需要这样操作,完全可以不这么设置。

(2)基类的析构函数设置为虚函数后,其派生出类的析构函数自动为虚函数。

3、把所有的类的析构函数都设置为虚函数好吗?

肯定不好,其实这就是想问虚函数的缺点。

虚函数属于在运行时进行处理的,为了在运行时根据不同的对象调用不同的虚函数,这就要求具有虚函数的类拥有一些额外信息,这些信息用于在运行时确定该对象应该调用哪一个虚函数。系统为每一个对象存储了一个虚函数表vtbl(virtual table)。虚函数表是一个函数指针数组,数组中每一个成员都包含一个虚函数,并把这个表的首地址存储在 vptr(virtual table pointer)中。当一个对象调用了虚函数,实际的被调用函数通过下面的步骤确定:找到对象的 vptr 指向的 vtbl,然后在 vtbl 中寻找合适的函数指针。

如图:

因此,使用虚函数后的类对象要比不使用虚函数的类对象占的空间多,而且在查找具体使用哪一个虚函数时,还会有时间代价。即当一个类不打算作为基类时,不用将其中的函数设置为虚函数。

 

转 C++构造函数、析构函数、虚函数之间的关系的更多相关文章

  1. C++ 构造函数 析构函数 虚函数

    C++:构造函数和析构函数能否为虚函数? 简单回答是:构造函数不能为虚函数,而析构函数可以且常常是虚函数. (1) 构造函数不能为虚函数 让我们来看看大牛C++之父 Bjarne Stroustrup ...

  2. JavaScript中的 原型 property 构造函数 和实例对象之间的关系

    1 为什么要使用原型? /* * javascript当中 原型 prototype 对象 * * */ //首先引入 prototype的意义,为什么要使用这个对象 //先来写一个构造函数的面向对象 ...

  3. C#中的虚函数及继承关系

    转载:http://blog.csdn.net/suncherrydream/article/details/8423991 若一个实例方法声明前带有virtual关键字,那么这个方法就是虚方法. 虚 ...

  4. 【M12】了解“抛出一个exception”与“传递一个参数”或“调用一个虚函数”之间的差异

    1.方法参数的声明语法和catch语句的语法是一样的,你可能会认为主调方法调用一个方法,并向其传递参数,与抛出一个异常传递到catch语句是一样的,是的,有相同之处,但也有更大的不同. 2.主调方法调 ...

  5. JavaScript中变量、参数、函数之间的关系

    ------------------------------ 废话不多说,直接开始. 我们看一段代码(参考其他资料所得) <script type="text/javascript&q ...

  6. python中lambda匿名函数与函数之间的关系

  7. C++ (P199—P211)多态 虚函数 抽象类

    在介绍多态之前,先回忆:赋值兼容原则.虚基类.二义性.派生类如何给基类赋值等知识. 在赋值兼容原则中:父类对象的指针赋给基类的指针或者父类的对象赋给基类的引用,可以通过强转基类的指针或者引用变为父类的 ...

  8. C++:构造函数和析构函数能否为虚函数

    原文:http://blog.csdn.net/xhz1234/article/details/6510568 C++:构造函数和析构函数能否为虚函数? 简单回答是:构造函数不能为虚函数,而析构函数可 ...

  9. 31.C++-虚函数之构造函数与析构函数分析

    1.构造函数不能为虚函数 当我们将构造函数定义为虚函数时,会直接报错: 首先回忆下以前学的virtual虚函数概念: 如果类定义了虚函数,创建对象时,则会分配内存空间,并且为该父类以及其所有子类的内存 ...

随机推荐

  1. NOIP2016——一个逗号引发的血案

    今年江西省报名人数一下子增起来了 隔壁中学来了80+人(虽然都是来给我们垫底的...临时被老师抓来上战场 总之我们赛区参赛人数总算多起来了(起码没再减50%...连续4年减50%真不是随便说说的... ...

  2. 【前端_js】Json对象和Json字符串的区别

    转载1: Json对象和Json字符串的区别 转载2: JSON字符串与JSON对象的区别

  3. 解决iPhone滑动不流畅问题

    前段时间在做一个手机端的页面时遇到了iOS上滑动不流畅的问题,后来才发现安卓上没有问题,才意识到这是兼容性问题引起的,所以遇到问题后快速定位到问题根源非常重要.在网上一搜就找到了解决方案.以后遇到类似 ...

  4. [译]The Python Tutorial#6. Modules

    [译]The Python Tutorial#Modules 6. Modules 如果你从Python解释器中退出然后重新进入,之前定义的名字(函数和变量)都丢失了.因此,如果你想写长一点的程序,使 ...

  5. Linux异常体系之vector_stub宏解析

    ARM-Linux汇编的宏定义语法说明如下: 使用注意: 1.宏定义以.macro开始,以.endm结束 2.可带参数,参数可有默认值 3.直接使用参数的名字\arg vector_stub宏的功能: ...

  6. oracle如何保证读一致性 第一弹

    oracle保证读一致性原理   1:undo segment的概念                   当数据库进行修改的时候,需要把保存到以前的old的数据保存到一个地方,然后进行修改,用于保存o ...

  7. NTC温度采集之数据拟合——freemat软件实现

    在stm32温度采样的过程中,使用到了NTC传感器,上拉接6.2K的电阻,信号给AD采样端口,通过NTC的电阻阻值表中,计算得到下面两端数据,在freemat中实现数据拟合,用于程序中温度和电压信号的 ...

  8. 线段树: CDOJ1598-加帕里公园的friends(区间合并,单点更新)

    加帕里公园的friends Time Limit: 3000/1000MS (Java/Others) Memory Limit: 131072/131072KB (Java/Others) 我还有很 ...

  9. Linux学习-系统基本设定

    网络设定 (手动设定与 DHCP 自动取得) 网络其实是又可爱又麻烦的玩意儿,如果你是网络管理员,那么你必须要了解局域网络内的 IP, gateway, netmask 等参数,如果还想要连上 Int ...

  10. HDU 5514 Frogs 欧拉函数

    题意: 有\(m(1 \leq m \leq 10^9)\)个石子排成一圈,编号分别为\(0,1,2 \cdots m-1\). 现在在\(0\)号石头上有\(n(1 \leq n \leq 10^4 ...