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

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. C++ Primer 学习笔记_61_重载操作符与转换 --自增/自减操作符

    重载操作符与转换 --自增/自减操作符 引言: 自增,自减操作符常常由诸如迭代器这种类实现,这种类提供相似于指针的行为来訪问序列中的元素.比如,能够定义一个类,该类指向一个数组并为该数组中的元素提供訪 ...

  2. Java并发专题 带返回结果的批量任务运行 CompletionService ExecutorService.invokeAll

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/27250059 普通情况下,我们使用Runnable作为主要的任务表示形式,可是R ...

  3. Android最方便的数据库--LitePal

    郭最近看到神分析LitePal相框,我感觉很强烈,尝试了一下,真的好,我不知道,如果你不习惯学习,那么各不相同,我觉得很合适 看完之后,思想,对于我来说,,实体到set颂值,如果数据非常多,那么你可以 ...

  4. young tableaus

    Young tableaus  这是 Introduction_to_algorithms一个 路学校运动, 我也难倒,互联网没有找到现有的应答. 今天 python 代码贴,供你参考. #! /us ...

  5. c++野指针 之 实战篇

    一:今天做poj上的3750那个题,用到了list的erase方法.提交之后总是报runtime error! 纠结了好长时间.曾有一度怀疑过vector的erase和list的erase处理方式不一 ...

  6. Linux 远程查看tomcat控制台

    我现在只说如何看远程的tomcat控制台命令. 用远程登陆客户端登陆linux进入tomcat/logs/文件夹下键入指令:tail -f catalina.out ctrl + c  退出 这样就可 ...

  7. poj 3280 Cheapest Palindrome ---(DP 回文串)

    题目链接:http://poj.org/problem?id=3280 思路: dp[i][j] :=第i个字符到第j个字符之间形成回文串的最小费用. dp[i][j]=min(dp[i+1][j]+ ...

  8. BZOJ 1038 ZJOI2008 瞭望塔 半平面交

    题目大意及模拟退火题解:见 http://blog.csdn.net/popoqqq/article/details/39340759 这次用半平面交写了一遍--求出半平面交之后.枚举原图和半平面交的 ...

  9. Python学习入门基础教程(learning Python)--3.1Python的if分支语句

    本节研究一下if分支语句. if分支语句是Python下逻辑条件控制语句,用于条件执行某些语句的控制操作,当if后的条件conditon满足时,if其下的语句块被执行,但当if的控制条件condito ...

  10. Openfire开发配置,Openfire源码配置,OpenFire二次开发配置

    1.下载源码:http://www.igniterealtime.org/downloads/source.jsp 2.把源码解压出的openfire_src目录放至eclipse workplace ...