一、构造函数为什么不能为虚函数

1. 从存储空间角度,虚函数相应一个指向vtable虚函数表的指针,这大家都知道,但是这个指向vtable的指针事实上是存储在对象的内存空间的。问题出来了,假设构造函数是虚的,就须要通过 vtable来调用,但是对象还没有实例化,也就是内存空间还没有,怎么找vtable呢?所以构造函数不能是虚函数。
2. 从使用角度,虚函数主要用于在信息不全的情况下,能使重载的函数得到相应的调用。构造函数本身就是要初始化实例,那使用虚函数也没有实际意义呀。所以构造函数没有必要是虚函数。虚函数的作用在于通过父类的指针或者引用来调用它的时候可以变成调用子类的那个成员函数。而构造函数是在创建对象时自己主动调用的,不可能通过父类的指针或者引用去调用,因此也就规定构造函数不能是虚函数。
3. 构造函数不须要是虚函数,也不同意是虚函数,由于创建一个对象时我们总是要明白指定对象的类型,虽然我们可能通过实验室的基类的指针或引用去訪问它但析构却不一定,我们往往通过基类的指针来销毁对象。这时候假设析构函数不是虚函数,就不能正确识别对象类型从而不能正确调用析构函数。
4. 从实现上看,vbtl在构造函数调用后才建立,因而构造函数不可能成为虚函数从实际含义上看,在调用构造函数时还不能确定对象的真实类型(由于子类会调父类的构造函数);并且构造函数的作用是提供初始化,在对象生命期仅仅运行一次,不是对象的动态行为,也没有必要成为虚函数。
5. 当一个构造函数被调用时,它做的首要的事情之中的一个是初始化它的VPTR。因此,它仅仅能知道它是“当前”类的,而全然忽视这个对象后面是否还有继承者。当编译器为这个构造函数产生代码时,它是为这个类的构造函数产生代码——既不是为基类,也不是为它的派生类(由于类不知道谁继承它)。所以它使用的VPTR必须是对于这个类的VTABLE。并且,仅仅要它是最后的构造函数调用,那么在这个对象的生命期内,VPTR将保持被初始化为指向这个VTABLE, 但假设接着另一个更晚派生的构造函数被调用,这个构造函数又将设置VPTR指向它的
VTABLE,等.直到最后的构造函数结束。VPTR的状态是由被最后调用的构造函数确定的。这就是为什么构造函数调用是从基类到更加派生类顺序的还有一个理由。可是,当这一系列构造函数调用正发生时,每一个构造函数都已经设置VPTR指向它自己的VTABLE。假设函数调用使用虚机制,它将仅仅产生通过它自己的VTABLE的调用,而不是最后的VTABLE(全部构造函数被调用后才会有最后的VTABLE)。



虚函数与非虚函数对照

  使用虚函数是有代价的,在内存和运行速度方面是有一定成本的,包含:

    l 
每一个对象都将增大,增大量为存储虚函数表指针的大小;

    l 
对于每一个类,编译器都创建一个虚函数地址表;

    l 
对于每一个函数调用,都须要运行一项额外的操作,即到虚函数表中查找地址。

  尽管非虚函数比虚函数效率稍高,单不具备动态联编能力

二、为什么基类的析构函数是虚函数?

  在实现多态时,当用基类操作派生类,在析构时防止仅仅析构基类而不析构派生类的状况发生。

  以下转自网络:源地址 http://blog.sina.com.cn/s/blog_7c773cc50100y9hz.html

  a.第一段代码

  

#include<iostream>
using namespace std;
class ClxBase{
public:
ClxBase() {};
~ClxBase() {cout << "Output from the destructor of class ClxBase!" << endl;}; 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(){
ClxDerived *p = new ClxDerived;
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的过程是:先释放继承类的资源,再释放基类资源.

  b.第二段代码

  

#include<iostream>
using namespace std;
class ClxBase{
public:
ClxBase() {};
~ClxBase() {cout << "Output from the destructor of class ClxBase!" << endl;}; 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(){
ClxBase *p = new ClxDerived;
p->DoSomething();
delete p;
return 0;
}

  输出结果:

  Do something in class ClxBase!

  Output from the destructor of class ClxBase!

这段代码中基类的析构函数相同不是虚函数,不同的是在main函数中用基类的指针去操作继承类的成员,释放指针P的过程是:仅仅是释放了基类的资源,而没有调用继承类的析构函数.调用  dosomething()函数运行的也是基类定义的函数.

普通情况下,这种删除仅仅可以删除基类对象,而不能删除子类对象,形成了删除一半形象,造成内存泄漏.

在公有继承中,基类对派生类及其对象的操作,仅仅能影响到那些从基类继承下来的成员.假设想要用基类对非继承成员进行操作,则要把基类的这个函数定义为虚函数.

析构函数自然也应该如此:假设它想析构子类中的又一次定义或新的成员及对象,当然也应该声明为虚的.

  c.第三段代码:

  

#include<iostream>
using namespace std;
class ClxBase{
public:
ClxBase() {};
virtual ~ClxBase() {cout << "Output from the destructor of class 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(){
ClxBase *p = new ClxDerived;
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()函数运行的也是继承类定义的函数.

假设不须要基类对派生类及对象进行操作,则不能定义虚函数,由于这样会添加内存开销.当类里面有定义虚函数的时候,编译器会给类加入一个虚函数表,里面来存放虚函数指针,这样就会添加类的存储空间.所以,仅仅有当一个类被用来作为基类的时候,才把析构函数写成虚函数.

构造函数为什么不能为虚函数 &amp; 基类的析构函数为什么要为虚函数的更多相关文章

  1. 基类的析构函数写成virtual虚析构函数

    虚函数作用:动态绑定,实现多态效果. 场景问题: 派生类中有资源需要回收,而在编程中采用多态,由基类的指针指向派生类,则在释放的时候,如果基类的析构函数不是virtual,则派生类的析构函数得不到释放 ...

  2. C++基类的析构函数定义为虚函数的原因

    1:每个析构函数只会清理自己的成员(成员函数前没有virtual).2:可能是基类的指针指向派生类的对象,当析构一个指向派生类的成员的基类指针,这时程序不知道这么办,可能会造成内存的泄露,因此此时基类 ...

  3. C++中基类的析构函数为什么要用virtual虚析构函数

    知识背景 要弄明白这个问题,首先要了解下C++中的动态绑定. 关于动态绑定的讲解,请参阅:  C++中的动态类型与动态绑定.虚函数.多态实现 正题 直接的讲,C++中基类采用virtual虚析构函数是 ...

  4. C++-基类的析构函数为什么要加virtual虚析构函数(转)

    知识背景 要弄明白这个问题,首先要了解下C++中的动态绑定. 关于动态绑定的讲解,请参阅:  C++中的动态类型与动态绑定.虚函数.多态实现 正题 直接的讲,C++中基类采用virtual虚析构函数是 ...

  5. OOP2(虚函数/抽象基类/访问控制与继承)

    通常情况下,如果我们不适用某个函数,则无需为该函数提供定义.但我们必须为每个虚函数都提供定义而不管它是否被用到了,这因为连编译器也无法确定到底会适用哪个虚函数 对虚函数的调用可能在运行时才被解析: 当 ...

  6. 【C++】C++中基类的析构函数为什么要用virtual虚析构函数?

    正面回答: 当基类的析构函数不是虚函数,并且基类指针指向一个派生类对象,然后通过基类指针来删除这个派生类对象时,如果基类的析构函数不是虚析构函数,那么派生类的析构函数就不会被调用,从而产生内存泄漏 # ...

  7. 读书笔记 effective c++ Item 7 在多态基类中将析构函数声明为虚析构函数

    1. 继承体系中关于对象释放遇到的问题描述 1.1 手动释放 关于时间记录有很多种方法,因此为不同的计时方法创建一个TimeKeeper基类和一些派生类就再合理不过了: class TimeKeepe ...

  8. C++虚复制构造函数,设置Clone()方法返回基类指针,并设置为虚函数

    构造函数不能是虚函数.但有时候确实需要能传递一个指向基类对象的指针,并且有已创建的派生类对象的拷贝.通常在类内部创建一个Clone()方法,并设置为虚函数. //Listing 12.11 Virtu ...

  9. C++ 虚函数在基类与派生类对象间的表现及其分析

    近来看了侯捷的<深入浅出MFC>,读到C++重要性质中的虚函数与多态那部分内容时,顿时有了疑惑.因为书中说了这么一句:使用“基类之指针”指向“派生类之对象”,由该指针只能调用基类所定义的函 ...

随机推荐

  1. Cocos2d-x串算出Size方法

    项目需要,根据所输入的字符串,的需要计算串帐户Size. 包代码如下面.只需要传递一个字符串,您可以返回Size: Size ChartDemoScene::calculateFontSize(con ...

  2. 不知道的JavaScript

    你不知道的JavaScript上卷笔记 前言 You don't know JavaScript是github上一个系列文章  初看到这一标题的时候,感觉怎么老外也搞标题党,用这种冲突性比较强的题目吸 ...

  3. Linux内核升级

    一.测试环境 CentOS6.5 X86 64位 内核版本为 2.6.32 VM 10.07 二.编译内核版本 2.1.kernel 3.2.71 2.2.kernel 3.4.108 2.3.ker ...

  4. 检查Linux Bash安全漏洞以及各环境修复解决方法

    第一.检测是否存在Bash漏洞$env x='() { :;}; echo vulnerable' bash -c "echo this is a test"如果返回以下内容:则请 ...

  5. Http协议学习总结(转)

    因为项目中很多地方都与Http协议有关,零散的了解了一下Http协议,但是没有系统的学习过. 今天根据网上其他同学的整理,加上我的一些经验,我也整理了一份.当做学习记录吧. 一.什么是HTTP协议 H ...

  6. struts2第一个程序的详解(配图)

    首先我们在struts2中要写上一个action <packagename="fish"namespace="/test"extends="st ...

  7. ThinkPHP中的volist标签中使用eq标签出错

    参考地址:http://blog.csdn.net/luquansen/article/details/18310855 源码: <volist id="v" name=&q ...

  8. PHP 文件操作类(创建文件并写入) 生成日志

    <?php /** * 文件操作(生成日志)支持多条插入 * (假设插入多条语句并换行 用','逗号分开) * */ class log { public $path = './info.txt ...

  9. VC6.0入门使用

    软件下载地址 http://pan.baidu.com/s/1qWuqFAO 新建win console 32 project,然后新建header文件.最后新建source cpp文件.如图所看到的

  10. 六:Java之集合

    集合包括的内容非常多,我发现一个非常好的博客,感觉自己都没有写的必要了! 链接献上  Java集合