转自:http://blog.csdn.NET/chen825919148/article/details/8020550
构造函数不能声明为虚函数,析构函数可以声明为虚函数,而且有时是必须声明为虚函数。
不建议在构造函数和析构函数里面调用虚函数。
构造函数不能声明为虚函数的原因是:
1 构造一个对象的时候,必须知道对象的实际类型,而虚函数行为是在运行期间确定实际类型的。而在构造一个对象时,由于对象还未构造成功。编译器无法知道对象 的实际类型,是该类本身,还是该类的一个派生类,或是更深层次的派生类。无法确定。。。
2 虚函数的执行依赖于虚函数表。而虚函数表在构造函数中进行初始化工作,即初始化vptr,让他指向正确的虚函数表。而在构造对象期间,虚函数表还没有被初 始化,将无法进行。
虚函数的意思就是开启动态绑定,程序会根据对象的动态类型来选择要调用的方法。然而在构造函数运行的时候,这个对象的动态类型还不完整,没有办法确定它到底是什么类型,故构造函数不能动态绑定。(动态绑定是根据对象的动态类型而不是函数名,在调用构造函数之前,这个对象根本就不存在,它怎么动态绑定?)编译器在调用基类的构造函数的时候并不知道你要构造的是一个基类的对象还是一个派生类的对象。
析构函数设为虚函数的作用:
解释:在类的继承中,如果有基类指针指向派生类,那么用基类指针delete时,如果不定义成虚函数,派生类中派生的那部分无法析构。
例:
#include "stdafx.h"
#include "stdio.h"
class A
{
public:
A();
virtual~A();
};
A::A()
{
}
A::~A()
{
printf("Delete class APn");
}
class B : public A
{
public:
B();
~B();
};
B::B()
{ }
B::~B()
{
printf("Delete class BPn");
}
int main(int argc, char* argv[])
{
A *b=new B;
delete b;
return 0;
}
输出结果为:Delete class B
Delete class A
如果把A的virtual去掉:那就变成了Delete class A也就是说不会删除派生类里的剩余部分内容,也即不调用派生类的虚函数
因此在类的继承体系中,基类的析构函数不声明为虚函数容易造成内存泄漏。所以如果你设计一定类可能是基类的话,必须要声明其为虚函数。正如Symbian中的CBase一样。
Note:1. 如果我们定义了一个构造函数,编译器就不会再为我们生成默认构造函数了。
2. 编译器生成的析构函数是非虚的,除非是一个子类,其父类有个虚析构,此时的函数虚特性来自父类。
3. 有虚函数的类,几乎可以确定要有个虚析构函数。
4. 如果一个类不可能是基类就不要申明析构函数为虚函数,虚函数是要耗费空间的。
5. 析构函数的异常退出会导致析构不完全,从而有内存泄露。最好是提供一个管理类,在管理类中提供一个方法来析构,调用者再根据这个方法的结果决定下一步的操作。
6. 在构造函数不要调用虚函数。在基类构造的时候,虚函数是非虚,不会走到派生类中,既是采用的静态绑定。显然的是:当我们构造一个子类的对象时,先调用基类的构造函数,构造子类中基类部分,子类还没有构造,还没有初始化,如果在基类的构造中调用虚函数,如果可以的话就是调用一个还没有被初始化的对象,那是很危险的,所以C++中是不可以在构造父类对象部分的时候调用子类的虚函数实现。但是不是说你不可以那么写程序,你这么写,编译器也不会报错。只是你如果这么写的话编译器不会给你调用子类的实现,而是还是调用基类的实现。
7. 在析构函数中也不要调用虚函数。在析构的时候会首先调用子类的析构函数,析构掉对象中的子类部分,然后在调用基类的析构函数析构基类部分,如果在基类的析构函数里面调用虚函数,会导致其调用已经析构了的子类对象里面的函数,这是非常危险的。
8. 记得在写派生类的拷贝函数时,调用基类的拷贝函数拷贝基类的部分,不能忘记了。
转自:http://blog.sina.com.cn/s/blog_7c773cc50100y9hz.html
1.第一段代码
#include<iostream>
using namespace std;
class ClxBase{
public:
};
class ClxDerived : public ClxBase{
public:
};
运行结果:
Do something in class ClxDerived!
Output from the destructor of class ClxDerived!
Output from the destructor of class ClxBase!
2.第二段代码
#include<iostream>
using namespace std;
class ClxBase{
public:
};
class ClxDerived : public ClxBase{
public:
};
输出结果:
Do something in class ClxBase!
Output from the destructor of class ClxBase!
3.第三段代码:
#include<iostream>
using namespace std;
class ClxBase{
public:
};
class ClxDerived : public ClxBase{
public:
};
运行结果:
Do something in class ClxDerived!
Output from the destructor of class ClxDerived!
Output from the destructor of class ClxBase!
转自:http://www.51projob.com/a/bishimianshi/2012/0414/195.html
其实这个问题最终将回答一个问题:
如果Base * pbase = new Derived;那么如果delete pbase的话,怎样避免内存泄露?
对比1:父类的普通成员函数和虚函数均是非虚函数
来看看这时候会发生什么,具体代码和运行结果如下:
- #include <iostream>
- using namespace std;
- //情景1:普通成员函数和析构函数,都不是虚函数
- class Base{
- public:
- Base(){}
- //父类的析构函数,不是虚函数
- ~Base(){
- cout<<"Base destructor"<<endl;
- }
- //普通成员
- void dosomething(){
- cout<<"do something in Base"<<endl;
- }
- };
- //派生类
- class Derived : public Base{
- public:
- Derived(){}
- ~Derived(){
- cout<<"Derived destructor"<<endl;
- }
- void dosomething(){
- cout<<"do something in Derived"<<endl;
- }
- };
- int main(){
- //这个时候,虽然父类指针实际指向子类,可是没有虚函数表,所以不能调用实际的类型,因此输出的只能是父类指针自身能看到的内容
- Base *base = new Derived;
- base->dosomething();//输出的是父类的函数
- delete base;//调用的是父类的析构函数
- system("pause");
- return 0;
- }
对比2:父类析构函数是虚函数(将调用哪个析构函数?)
将代码的父类的虚构函数加上virtual关键字,其他完全相同,得到如下代码和运行结果:
- #include <iostream>
- using namespace std;
- //情景1:普通成员函数和析构函数,都不是虚函数
- class Base{
- public:
- Base(){}
- //父类的析构函数,是虚函数,只做了这里的更改
- virtual ~Base(){
- cout<<"Base destructor"<<endl;
- }
- //普通成员
- void dosomething(){
- cout<<"do something in Base"<<endl;
- }
- };
- //派生类
- class Derived : public Base{
- public:
- Derived(){}
- ~Derived(){
- cout<<"Derived destructor"<<endl;
- }
- void dosomething(){
- cout<<"do something in Derived"<<endl;
- }
- };
- int main(){
- //这个时候,虽然父类指针实际指向子类,可是没有虚函数表,所以不能调用实际的类型,因此输出的只能是父类指针自身能看到的内容
- Base *base = new Derived;
- base->dosomething();//输出的是父类的函数
- delete base;//调用的是父类的析构函数
- system("pause");
- return 0;
- }
相关小结
对于Base *pbase = new Derived;
- 如果父类函数不是析构函数,那么pbase只能“看见”父类本身的函数,这是因为没有虚函数表让它可以找到本身
- 如果父类析构函数是虚函数,如果delete pbase,将会先调用子类函数的析构函数,然后子类析构函数自动调用父类的析构函数,真正实现了资源释放,防止了内存泄露
- 构造派生类的时候,会先构造基类部分,然后构造子类部分;撤销派生类对象的时候,会先撤销派生类部分,然后撤销基类部分