C++的继承体系给我们的程序带来了很多奇特的现象,看看下面这段代码:
class CBase
{
public:
CBase();
~CBase(){std::cout << "析构基类" << std::endl;}
};
//派生类
class CSon1 : public CBase
{
public:
CSon1();
~CSon1();{std::cout << "析构派生类" << std::endl;}
}
......
CBase *pB = new CSon1();
......
delete pB;
我们定义了一个基类CBase和一个派生类CSon1,然后我们声明了一个基类的指针pB,并对其赋值以派生类的对象,指针被使用完之后,调用了delete对其资源进行释放,一切看起来都很正常,最终程序运行的结果是:
析构基类
这似乎跟我们预想的不一致,我们通过delete释放资源,最终只有基类的析构函数被执行,如果派生类中申请了大量的资源,将无法得到释放。
问题出在基类CBase中有一个非虚析构函数。在C++的语法规则中,当派生类经由一个基类指针被删除,而该基类带着一个非虚析构函数,实际执行时通常发生的是对象的派生成分没有被销毁。上述问题解决的办法是将基类CBase的析构函数声明为虚函数:
virtual ~CBase();
......
virtual ~CSon1();
再次运行程序得到输出结果:
析构派生类
析构基类
在写程序中,一般情况下,将基类的析构函数定义成虚函数是一个好的习惯。
对于类的成员函数,虚函数也影响函数的调用结果,看看下面这段代码:
class CBase
{
......
void func1()
{
std::cout << "CBase::func1" << std::endl;
}
};
class CSon1 : public CBase
{
......
void func1()
{
std::cout << "CSon1 ::func1" << std::endl;
}
};
还是上面我们提到的两个类CBase和CSon1,我们在基类和派生类中都添加了一个同名的非虚函数func1(),我们通过以下两种方式来调用func1()函数:
CSon1 son;
//方法1
CBase *pB = &son;
pB->func1();
//方法2
CSon1 *pS = &son;
pS->func1();
这两种方法的区别是它们分别使用了基类指针和派生类指针调用了func1(),而两个指针指向了同一个对象son。我们可能会这样认为:这两个指针都通过son调用了func1()函数,他们的的行为应该相同。情况是这样吗,我们看看程序的执行结果:
CBase::func1
CSon1::func1
真是结果与我们预想的不一样,程序分别执行了基类和派生类的func1()函数,而造成这一结果的原因是fun1()是非虚函数。非虚函数都是静态绑定的,也就是说,通过pB调用非虚函数永远是CBase所定义的版本,即使pB指向了一个派生类对象。另一方面,虚函数则是动态绑定的,不论是通过基类指针,还是派生类指针,最终都会调用派生类的函数CSon1::func1()。
在上述的代码中,我们将func1()声明为virtual函数,执行结果是:
CSon1::func1
CSon1::func1
从这个例子中,我们可以学习到:如果基类中的函数为非虚函数,我们不要尝试去重新定义它。
还有一个由C++继承带来的问题,看看下面的代码:
class CBase
{
......
virtual void func1(BOOL bPrint = TRUE)
{
if(bPrint)
std::cout << "CBase::func1" << std::endl;
}
};
class CSon1 : public CBase
{
......
virtual void func1(BOOL bPrint = FALSE)
{
if(bPrint)
std::cout << "CSon1 ::func1" << std::endl;
}
};
......
CBase *pB = new CSon1();
pB->func1();
程序中,pB的动态类型是CSon1*,所以调用的是CSon1::func1()的缺省参数,而pB的静态类型是CBase*,所以此一调用的缺省参数来自CBase::func1()。编译器基于最大化程序执行效率的考虑,最终选择了静态类型的缺省参数。程序执行的结果为:
CSon1 ::func1
这也就告诉我们,程序中尽量不要重新定义基类的缺省参数,这样可能得不到你想要的结果。