构造函数跟虚构函数里面都可以调用虚函数,编译器不会报错。
C++ primer中说到最好别用
由于类的构造次序是由基类到派生类,所以在构造函数中调用虚函数,虚函数是不会呈现出多态的
类的析构是从派生类到基类,当调用继承层次中某一层次的类的析构函数时意味着其派生类部分已经析构掉,所以也不会呈现多态
因此如果在基类中声明的纯虚函数并且在基类的析构函数中调用之,编译器会发生错误。
class Base
{
public:
Base()
{
Fuction();
}
virtual void Fuction()
{
cout << "Base::Fuction" << endl;
}
};
class A : public Base
{
public:
A()
{
Fuction();
}
virtual void Fuction()
{
cout << "A::Fuction" << endl;
}
};
这样定义一个A的对象,会输出什么?
A a;
首先调用应该是没有问题的,但是得到的结果呢?
很多人会说输出:A::Fuction A::Function. 他们会觉得在 先调用父类构造函数时, 是子类对象去调用的Function.
如果是按照上面的情形进行输出的话,那就是说在构造Base的时候,也就是在Base的构造函数中调用Fuction的时候,调用了子类A的Fuction,而实际上A还没有开始构造,这样函数的行为就是完全不可预测的,因此显然不是这样,实际的输出结果是:
Base::Fuction
A::Fuction
本意是想实现 对应的 对象调用虚函数, 能实现对应的析构函数(调用虚函数的虚析构)
#include using namespace std;
class A
{
public:
A()
{
cout << "A构造函数";
Test();
}
~A()
{
cout << "A析构函数";
cout << "A::Test()" << endl;
}
virtual void Test()
{
cout << "A::Test()" << endl;
}
};
class B:public A
{
public:
B()
{
cout << "B构造函数";
Test();
}
~B()
{
cout << "B析构函数";
Test();
}
virtual void Test()
{
cout << "B::Test()" << endl;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
A* pA = new B(); 调用构造函数 输出A构造函数:A::Test() B调用构造函数B::Test()
cout << "动态调用:";
pA->Test(); 原指针类型是PA,实际指针类型是B,由于是虚函数,所以按实际类型调用 B::Test()
delete pA; 由于A的析构不是虚函数,所以按照原类型指针调用,如果在A的析构函数中加上virtual 则输出为B析构函数B::Test A析构函数A::Test
return 0;
}
#include
using namespace std;
class A
{
public:
void virtual f()
{
cout<<"f() A"<<endl;
}
void func()
{
cout<<"func() A"<<endl;
}
A()
{
cout<<"A()"<<endl;
}
virtual ~A()
{
cout<<"~A()"<<endl;
}
};
class B : public A
{
public:
void virtual f()
{
cout<<"B"<<endl;
}
void func()
{
cout<<"func() B"<<endl;
}
B()
{
cout<<"B()"<<endl;
}
virtual ~B()
{
cout<<"~B()"<<endl;
}
};
int main ()
{
A* pa=new A();
pa->f(); //这个很明显A
pa->func();
B* pb=(B*)pa;
pb->f(); //这个强制将pa复制到pb,所以pb实际指向A
pb->func();
/*输出结果 虚函数根据指针实际类型调用,其他函数都是根据原类型调用。没有调用B的构造函数,却调用了B类中的对象
A()
f() A
func() A
f() A
func() B
*/
delete pa;
delete pb;
/*
~A()
只会调用A的析构函数// 因为B都没构造
*/
return 0;
}
补充:
构造函数不能为虚函数,而析构函数可以且常常是虚函数。
这就要涉及到C++对象的构造问题了,C++对象在三个地方构建:
(1)函数堆栈;(2)自由存储区,或称之为堆;
(3)静态存储区。无论在那里构建,其过程都是两步:首先,分配一块内存;其次,调用构造函数。好,问题来了,如果构造函数是虚函数,那么就需要通过vtable 来调用,但此时面对一块 raw memeory,到哪里去找 vtable 呢?毕竟,vtable 是在构造函数中才初始化的啊,而不是在其之前。因此构造函数不能为虚函数。
这个就好理解了,因为此时 vtable 已经初始化了;况且我们通常通过基类的指针来销毁对象,如果析构函数不为虚的话,就不能正确识别对象类型,从而不能正确销毁对象。
构造函数不能声明为虚函数,析构函数可以声明为虚函数,而且有时是必须声明为虚函数。不建议在构造函数和析构函数里面调用虚函数。
构造函数不能声明为虚函数的原因是:
1 构造一个对象的时候,必须知道对象的实际类型,而虚函数行为是在运行期间确定实际类型的。而在构造一个对象时,由于对象还未构造成功。编译器无法知道对象 的实际类型,是该类本身,还是该类的一个派生类,或是更深层次的派生类。无法确定。。。
2 虚函数的执行依赖于虚函数表。而虚函数表在构造函数中进行初始化工作,即初始化vptr,让他指向正确的虚函数表。而在构造对象期间,虚函数表还没有被初 始化,将无法进行。
虚函数的意思就是开启动态绑定,程序会根据对象的动态类型来选择要调用的方法。然而在构造函数运行的时候,这个对象的动态类型还不完整,没有办法确定它到底是什么类型,故构造函数不能动态绑定。(动态绑定是根据对象的动态类型而不是函数名,在调用构造函数之前,这个对象根本就不存在,它怎么动态绑定?)
编译器在调用基类的构造函数的时候并不知道你要构造的是一个基类的对象还是一个派生类的对象。
析构函数设为虚函数的作用:
解释:在类的继承中,如果有基类指针指向派生类,那么用基类指针delete时,如果不定义成虚函数,派生类中派生的那部分无法析构。