NVI(Non-Virtual Interface)手法

国庆期间在家里把刚到手的《E C++》第三版看了一遍,对比自己之前读的第二版明显加了很多东西,在此记录以下在其中提到的virtual函数之外的选择——NVI手法
virtual关键字涉及多态,了解其是非常必要的,在此篇博客主要围绕virtual容易忽略的点展开。并且,将分点介绍NVI的两种使用情况:
1.在调用真正体现虚函数之前或之后进行一些你想要进行的操作,而不是将这个烂摊子丢给客户端去完成
2.当你需要在迟绑定(运行期多态)的前提下,同时体现early binding(缺省参数) (光看标题可能难以理解)
首先,对于下述类

class CHero
{
public:
  virtual int GetHeart() = 0;
};

这边以大家熟悉的英雄作为背景,以基类(虚基类)CHero派生出坦克类CTank,由于基类中的虚函数是个纯虚函数,故在其派生类中必须提供一个GetHeart()的实现如

class CTank :public CHero//坦克类返回其血量的两倍,血量通过构造函数获得
{
public:
	explicit CTank(int aim) :hero_heart(aim) {}
private:
	virtual int GetHeart()
	{
		return hero_heart * 2;
	}
private:
	int hero_heart;
};

如上便是一个简单的多态实现,但是现在出现一个要求:你必须在执行GetHeart()前,执行一段代码,打印输出字符串"HP:",简单的处理是直接在客户端的调用此函数的地方之前,加入要求的代码。为了让客户省心,于是便可用到NVI手法如下:
修改基类

class CHero
{
public:
	int MyGetHeart()
	{
		string temp = "HPP:";
		cout << temp << this->GetHeart() << endl;
		cout << "在此函数体可以在操作虚函数之外再执行其他的语句" << endl;
	}
	
private:
	virtual int GetHeart() = 0;
};

修改派生Tank类

class CTank :public CHero
{
public:
	explicit CTank(int aim) :hero_heart(aim) {}
private:
	virtual int GetHeart()
	{
		return hero_heart * 2;
	}
private:
	int hero_heart;
};

比较以下使用NVI手法前后的区别,在基类CHero中,GetHeart()被声明为private的(因为其仅仅被自己的成员函数所调用,将其声明为public会破坏类的封装性),通过一个新的函数MyGetyHeart()调用,并在其中添加要求的代码。这在实际应用中的好处不言而喻,你可以在virtual函数执行前添加互斥锁,日志功能,而你的客户端仅需要更改以下其调用的函数名即可。
“derived class 可重新定义继承而来的private virtual函数”这一规则在之前自己并没有看到过,上述的方法很好的阐述了这一规则,故在此记录。

下面介绍NVI手法的第二个使用方法,当你需要在迟绑定(运行期多态)的前提下,同时体现early binding(缺省参数)。这一点在《E C++》书中被题为“绝不重新定义继承而来的缺省值”。原理如下
例如:

#include 
#include 
using namespace std;
class A
{
public:
	virtual int GetValue(int x = 0)
	{
		return x;
	}
};
class B :public A
{
	virtual int GetValue(int x = 1)
	{
		return x;
	}
};
int main()
{
	A* a = new A();
	cout << a->GetValue() << endl;
	
	A* b = new B();
	cout << b->GetValue() << endl;
	cout << "Hello" << endl;
}

此段代码在本地编译器调试通过,令人难以置信的是其输出结果竟然是两个0!我明明以父类指针定义了两个对象,按照多态的原理,父类指针指向子类对象时,其应该调用真正的子类的成员函数,但是我在执行b->GetValue()时,竟然返回的x是0,而不是预期的1.解释:因为动态类型和静态类型的区分。指针变量a b他们的静态类型都是声明的A* 只有在运行时,他们才表现出他们的动态类型,即具体的A和B。所以,调用一个virtual函数时,他们表现出自己的动态类型,但是,缺省参数值是一种静态绑定,故他们体现出自己的静态类型,即A* 。函数缺省值为A的成员函数的缺省值(x=0),虽然b对象确确实实是b的成员函数(不信可以修改b的成员函数,我已经实践过),但是其缺省值却是A中的缺省值,若不了解这点,可能会在以后碰到是怀疑编译器出错。
了解这一点后,就可以很轻易地相处如何在派生类中拥有自己的缺省参数,结合NVI方法。即尝试“将缺省参数写在 NVI方法的public函数中(即那个调用虚函数的成员函数)”这样,就可以在普通函数中进行early binding,在virtual函数中,实现late binding。从而避免虚函数的缺省参数的精神失常问题。

你可能感兴趣的:(个人学习,C++,虚函数)