标签:
virtual , 写C++ 的都不会陌生吧,用于类的成员函数,用以表现对象多态的性质。
以前看书时,得到一条“黄金定律”(这是错误的):
永远应该以virtual 声明类的析构函数
如果不这么做,那么当类成为基类时,在回收对象内存,会发生不正确的行为,导致内存泄漏。这里就不在赘述细节了。
乍一看,很有道理不是么,防患于未然。
但是少年呀,不能这么年轻,轻易声明virtual 是要付出代价的。
在C++中,virtual 的运行时判断行为是基于类的虚函数表实现的。虚函数表是要耗费内存的,而且也会产生效率问题。
所以,正确地使用virtual 是[1]:
polymorphic(带多态性质的)base class 应该声明一个virtual 析构函数。如果class 带有任何virtual 函数,它就应该拥有一个virtual 析构函数
Classes 的设计目的如果不是作为base classed 使用,或不是为了具备多态性,就不该声明virtual 析构函数
恰当的时候,声明virtual 析构函数是正确的。
现在考虑抽象类的声明。
抽象类定义为类中至少包含一个pure-virtual 函数(纯虚函数)。抽象类作为基类,也应该有一个virtual 析构函数。
于是,在实现上,我们可以取巧,干脆就声明一个pure-virtual 的析构函数好了:
class SomeClass
{
public:
virtual ~SomeClass() = 0;
};
SomeClass::~SomeClass() {} // 然而还是要提供定义
这是[1]中提到的一个技巧。
假如,我们希望在类被构造的同时做一些操作,类似于log,或者更新 什么与该实例化对象相关的操作。
嗯,就让类在构造的时候,打印自己的类名好了。
等等,那如果类被继承了呢,而我又想把这个操作从构造函数中分离出来。
ok,仔细一想,于是就让那么函数成为virtual 好了。于是就有下如下的实现:
class Base
{
public:
Base() { print(); } //注意这里调用了virtual 函数
virtual ~Base(){}
virtual void print() = 0;
};
void Base::print()
{ cout<<" Base "<<endl; }
class Derived: public Base
{
public:
Derived(){}
void print() { cout<<" Derived "<<endl; }
};
*这个实现是不正确的
那么,当我们实例化Derived class 的时候,会发生什么?
Derived d;
我们以为会输出
Derived
然而真正的行为却是
Base
为什么virtual 函数在这时候的行为表现得却不是virtual函数?
因为在base class构造期间,virtual 函数不是virtual 函数。[1]
答案就是那么直白,所以,对于上述的行为,print 会调用的版本才是Base 的版本,因为在执行到那一句的过程时,Derived class 的类型其实是Base class 。[1]
事实上,在编译的时候,会有warning 信息:
warning: pure virtual ‘virtual void Base::print()’ called from constructor
Base() { print(); } //注意这里调用了virtual 函数
解释也是很易懂:base 的构造函数早于derived 构造函数,当base 的构造函数执行的时候,derived 的成员变量处于未初始化阶段。如果virtual 函数下降至 derived class阶层,那么取得的成员变量是derived class 的(但它们却没有初始化),这会导致程序的不明确行为。
对应于析构函数,道理其实是一样的,这里不再解释了。
当类发生继承时,会继承基类的成员变量、成员函数。
对于成员函数的继承,又可以有pure-virtual、impure-virtual、non-virtual的区别。(在本文中,如果单指virtual 函数,那么表示的是impure-virtual)
而对于这些继承,含义区分如下[1]:
*这部分内容来自[1]条款35。
有以下定义:
class Base
{
public:
virtual ~Base(){}
virtual void func(int i=3)
{
cout<<"Base : "<<i<<endl;
}
};
class Derived: public Base
{
public:
void func(int i)
{
cout<<" Derived : "<<i<<endl;
}
};
如果有:
Base b;
Derived d;
d.func(); //能够通过编译吗
答案是不行。
当通过对象的静态绑定调用func 的时候,必须要指定参数值。
ok,那么现在有这样的调用:
Base* pbd = &d;
pbd->func(); //ok?
这是可行的,并且运行的是Derived 版本的函数。
也就是说,输出是:
Derived : 3
在[1] 中的解释如下:
若以对象调用此函数,一定要指定参数值。
因为静态绑定下,函数并不从其base继承缺省参数值。
但若以指针或引用调用此函数,可以不指定参数,因为动态绑定下,这个函数会从其base 继承缺省参数值。
很神奇不是吗,一个函数的调用结果是由其base 的缺省参数和derived 的实现一起贡献的。
现在,做一点改变,假如:
class Derived: public Base
{
public:
void func(int i=4) //这里也指定了缺省参数
{
cout<<"Derived :"<<i<<endl;
}
};
当有:
Base* pbd = &d;
pbd->func();
结果会是什么?
输出的是3,也就是继承自base 的缺省参数。
这样的演示是为了说明语法方面的问题。但从设计上来说,这并不合理。
多个不同的缺省参数,导致代码难以阅读,行为也不清晰。
那如果指定了一样的缺省参数呢?
class Derived: public Base
{
public:
void func(int i=3) //这里指定和base 一样的缺省参数
{
cout<<"Derived :"<<i<<endl;
}
};
也不合理,代码重复是个尴尬的问题,如果base中的缺省参数修改了,那么derived 中的缺省参数是不是也要一一修改。
其实,保持已经一开始的版本就好,但是在使用的时候,你必须心中有数,清楚地明白它的行为是怎么样的。
另外一种设计的替代方案是,采用NVI(non-virtual interface) 手法:令base class 内的一个public non-virtual 函数调用private virtual 函数,后者可被derived class 重新定义。
class Base
{
private:
virtual void doFunc(int i)
{
cout<<"Base : "<<i<<endl;
}
public:
virtual ~Base(){}
void func(int i=3)
{
doFunc(i);
}
};
class Derived: public Base
{
private:
virtual void doFunc(int i)
{
cout<<"Derived :"<<i<<endl;
}
public:
};
最后,重申:
绝对不要重新定义一个继承而来的缺省参数值[1]
[参考资料]
[1] Scott Meyers 著, 侯捷译. Effective C++ 中文版: 改善程序技术与设计思维的 55 个有效做法[M]. 电子工业出版社, 2011.
(条款07:为多态基类声明virtual 析构函数;
条款09:绝不在构造和析构过程中调用virtual函数;
条款34:区分接口继承和实现继承;
条款37:绝对不要重新定义继承而来的缺省参数值)
标签:
原文地址:http://blog.csdn.net/u014613043/article/details/51324587