继承中虚函数的缺省参数值问题

如果类继承中重新定义了虚函数,那么虚函数中的缺省参数不要重新定义。

用一句话来解释原因就是:虚函数是动态绑定的(dynamically bound),但是缺省参数却是静态绑定的(statically bound)。

静态类型和动态类型

首先需要了解什么是对象的静态类型和动态类型,对象的所谓静态类型(static type),就是它在程序中被声明时所采用的类型。

以下面的类为例:

class Shape
{
	public:
	enum ShapeColor{Red,Green,Blue};

	virtual void draw(ShapeColor color=Red) const =0;
};

class Rectangle :public Shape
{
	public:
			virtual void draw(ShapeColor color=Green) const;
};

class Circle:public Shape
{
	public:
			//当以对象调用这个函数时,一定要指定参数值。
			//因为静态绑定下这个函数并不从其base继承缺省的参数值
			//但若以指针或引用调用此函数,可以不指定参数值,
			//因为动态绑定下这个函数会从其base继承缺省参数值
			virtual void draw(ShapeColor) const;
};

考虑下面这些指针:

	Shape *ps;//静态类型是Shape*
	Shape *pc=new Circle;//静态类型是Shape*
	Shape *pr=new Rectangle;//静态类型是Shape*

ps,pc,pr都被声明为point to Shape类型,所以他们都以它为静态类型。

对象的所谓动态类型(dynamic type)则是指它“目前所指向对象的类型”。也就是说,动态类型可以表现出一个对象将会有什么行为。以上例,pc的动态类型是Circle *,pr的动态类型是Rectangle *。ps没有动态类型,因为它尚未指向任何对象。

动态类型和它的名字所示,可以在程序执行过程中改变(通过赋值操作符)

pc=pr;//这条语句把pc的动态类型由Circle*改成了Rectangle*

个人感觉C++中的静态类型和动态类型和C中指针的类型(静态类型)和指针所指向数据的类型(动态类型)是类似的。

虚函数动态绑定和缺省参数静态绑定

静态绑定又叫前期绑定,early bounding;动态绑定又叫后期绑定,late binding。

虚函数系动态绑定而来,意思是调用一个虚函数时,究竟调用那一份函数实现代码,取决于发出调用的那个对象的动态类型。

如果函数的实现如下:

void Rectangle::draw(ShapeColor color) const{
	switch (color)
	{
		case Green:
			{
			std::cout<<"rectangle draw green"<<std::endl;
			break;
			}
		case Red:
			{
			std::cout<<"rectangle draw red"<<std::endl;
			break;
			}
		default:
			break;
	}
}
void Circle::draw(ShapeColor color) const{
	switch (color)
	{
		case Green:
			{
			std::cout<<"circle draw green"<<std::endl;
			break;
			}
		case Red:
			{
			std::cout<<"circle draw red"<<std::endl;
			break;
			}
		default:
			break;
	}
}

那么pc和pr分别调用draw时,是根据pc和pr的动态类型Circle *和Rectangle *确定调用那个draw函数:

	pc->draw(Shape::Red);
	pr->draw(Shape::Red);

但是如果不为上面的draw函数指定参数而使用缺省的参数,就又会有问题,因为虚函数是动态绑定的,而缺省函数是静态绑定的。结果就是调用一个定义于继承类中的虚函数但却使用的是基类中的缺省值。

如下:

	pr->draw();

pc的动态类型是Rectangle *,它的虚函数draw中重新定义了缺省参数值是Green,但是通过上面的函数调用的结果如下:也就是说它调用的是Rectangle类的draw函数,但是使用的是Shape类的缺省值。这是由于pc的静态类型是Shape,而虚函数中的默认参数是静态绑定,所以默认参数是Shape中定义的。

C++中采用虚函数动态绑定,缺省参数静态绑定的原因在于运行期效率。如果缺省参数值是动态绑定,编译器就必须有某种方法在运行期为虚函数决定适当的参数缺省值。这比目前实行的在编译期决定的机制更慢而且更复杂。为了程序的执行速度和编译器实现上的简易度,C++做了这样的取舍。

上面还有一个问题就是Circle类中的draw没有定义默认参数,那么可能想的就是这种写法应该是正确的,它继承父类中的默认参数,但是实际上不完全是如此的。如果是以指针的形式调用这个函数,它的工作方式和想象中是一样的(即如何没有给draw指定实参,那么使用base类的缺省参数),但是如果以对象的形式使用这个函数,它在编译时就会报错

	Circle oc;
	oc.draw();

在g++中编译错误如下:

当以对象调用这个函数时,一定要指定参数值。因为静态绑定下这个函数并不从其base继承缺省的参数值但若以指针或引用调用此函数,可以不指定参数值,因为动态绑定下这个函数会从其base继承缺省参数值。

那么如果在父类的虚函数中指定有缺省参数,子类如果正好要重写这个虚函数,造成的结果就是只能把缺省的参数复制一遍,并且不能修改(如上),但这样会造成大量的重复代码。

为了解决上面的问题,需要采用下面的方法

解决虚函数缺省参数值的方法

让它表现正常的做法就是采用使用NVI(non-virutual interface)手法:令基类中的public non-virtual函数调用private virtual函数,后者可被derived class重新定义。这里可以让non-virtual函数指定缺省参数,而private virtual函数负责真正的工作:

class Shape
{
	public:
	enum ShapeColor{Red,Green,Blue};

	void paint(ShapeColor color=Red) const
	{
		doPaint(color);//调用一个virtual函数
	}

	private:
	virtual void doPaint(ShapeColor color) const =0;
};

class Rectangle :public Shape
{
	private:
	virtual void doPaint(ShapeColor color) const ;
};

class Circle:public Shape
{
	private:
	virtual void doPaint(ShapeColor color) const ;

};

由于non-virtual函数绝不应该被重写,这样使得paint函数的color缺省值总是Red,解决了上面的问题。

完整的代码如下:

#include <stdlib.h>
#include <stdio.h>
#include <iostream>

//对象的静态类型(static type),就是它在程序中被声明时所采用的类型
class Shape
{
	public:
	enum ShapeColor{Red,Green,Blue};

	virtual void draw(ShapeColor color=Red) const =0;

	void paint(ShapeColor color=Red) const
	{
		doPaint(color);//调用一个virtual函数
	}

	private:
	virtual void doPaint(ShapeColor color) const =0;
};

class Rectangle :public Shape
{
	public:
			virtual void draw(ShapeColor color=Green) const;
	private:
	virtual void doPaint(ShapeColor color) const ;
};

class Circle:public Shape
{
	public:
			//当以对象调用这个函数时,一定要指定参数值。
			//因为静态绑定下这个函数并不从其base继承缺省的参数值
			//但若以指针或引用调用此函数,可以不指定参数值,
			//因为动态绑定下这个函数会从其base继承缺省参数值
			virtual void draw(ShapeColor) const;
	private:
	virtual void doPaint(ShapeColor color) const ;

};

void Rectangle::draw(ShapeColor color) const{
	switch (color)
	{
		case Green:
			{
			std::cout<<"rectangle draw green"<<std::endl;
			break;
			}
		case Red:
			{
			std::cout<<"rectangle draw red"<<std::endl;
			break;
			}
		default:
			break;
	}
}
void Circle::draw(ShapeColor color) const{
	switch (color)
	{
		case Green:
			{
			std::cout<<"circle draw green"<<std::endl;
			break;
			}
		case Red:
			{
			std::cout<<"circle draw red"<<std::endl;
			break;
			}
		default:
			break;
	}
}

void Rectangle::doPaint(ShapeColor color) const{
	std::cout<<"rectangle doPaint "<<color<<std::endl;
}
void Circle::doPaint(ShapeColor color) const{
	std::cout<<"circle doPaint "<<color<<std::endl;
}

int main()
{

	Shape *ps;//静态类型是Shape*
	Shape *pc=new Circle;//静态类型是Shape*
	Shape *pr=new Rectangle;//静态类型是Shape*

	pr->paint();
//	pc->draw(Shape::Red);
//	pr->draw();

//	pr->draw();

//	Circle oc;
//	oc.draw();
	return 0;
}
时间: 2024-11-05 17:22:38

继承中虚函数的缺省参数值问题的相关文章

&lt;转&gt;C++继承中虚函数的使用

转自:http://blog.csdn.net/itolfn/article/details/7412364 一:继承中的指针问题. 1. 指向基类的指针可以指向派生类对象,当基类指针指向派生类对象时,这种指针只能访问派生对象从基类继承 而来的那些成员,不能访问子类特有的元素 ,除非应用强类型转换,例如有基类B和从B派生的子类D,则B *p;D  dd; p=&dd;是可以的,指针p只能访问从基类派生而来的成员,不能访问派生类D特有的成员.因为基类不 知道派生类中的这些成员. 2. 不能使派生类

C++ 在继承中虚函数、纯虚函数、普通函数,三者的区别

1.虚函数(impure virtual) C++的虚函数主要作用是“运行时多态”,父类中提供虚函数的实现,为子类提供默认的函数实现. 子类可以重写父类的虚函数实现子类的特殊化. 如下就是一个父类中的虚函数: class A { public: virtual void out2(string s) { cout<<"A(out2):"<<s<<endl; } }; 2.纯虚函数(pure virtual) C++中包含纯虚函数的类,被称为是“抽象类

C++ 类在继承中虚函数、纯虚函数、普通函数,三者的区别

1.虚函数(impure virtual) C++的虚函数主要作用是"运行时多态",父类中提供虚函数的实现,为子类提供默认的函数实现. 子类可以重写父类的虚函数实现子类的特殊化. 如下就是一个父类中的虚函数: class A { public:     virtual void out2(string s)     {         cout<<"A(out2):"<<s<<endl;     } }; 2.纯虚函数(pure

绝不重新定义继承而来的函数的缺省参数值

首先,此处的函数指的是 virtual 函数,为什么不是non-virtual函数呢?是因为在public继承中,non-virtual函数表示派生类是需要继承其接口与其强制实现的.如果你的Derived已经在考虑重写non-virtual函数了,那么你是应该好好审视一下,在此处使用 public继承是否合理了. 因此,本文只讲解virtual函数的缺省参数值,例如下面的代码: 在C++代码中,函数的参数值是静态绑定的,而通过基类的指针或者引用对virtual函数调用的动态绑定的. 因此,当你的

C++继承、虚函数处的面试题

昨天,收到 SenseTime公司面试官的电话面试(一天面了三家公司,收获挺多的),通话时间将近1个半小时,面试过程中暴露出很多知识上的漏洞,本篇文章针对面试过程中继承以及虚函数方面的知识做一总结,查缺补漏,希望对大家有帮助. 单继承下的虚函数表 //单继承下虚函数表:是如何组织的 class A{ public: virtual void func(){ cout << "A::func" << endl; } virtual void funcA(){ co

C++ 虚函数的缺省参数问题

前些日子,有个同学问我一个关于虚函数的缺省参数问题.他是从某个论坛上看到的,但是自己没想通,便来找我.现在分享一下这个问题.先看一小段代码: #include <iostream> using namespace std; class A { public: virtual void Fun(int number = 10) { cout << "A::Fun with number " << number; } }; class B: public

C++多态中虚函数的深入理解

c++中动态多态性是通过虚函数来实现的.静态多态性是通过函数的重载来实现的,在程序运行前的一种早绑定,动态多态性则是程序运行过程中的一种后绑定.根据下面的例子进行说明. #include <iostream> #include <string> using namespace std; class Shape//形状类 { public: double calcArea() { cout<<"calcArea"<<endl; return

C++中虚函数的动态绑定和多态性

目录 静态类型VS动态类型,静态绑定VS动态绑定两组概念 虚函数的实现机制 多态性 一.静态 vs 动态 静态类型 VS 动态类型.静态类型指的是对象声明的类型,在编译器确定的.动态类型指的是对象的所指向的类型,动态类型是可以更改的,静态类型无法更改.继承关系会导致对象的指针和引用具有静态类型和动态类型,因为继承关系的存在中可能会存在类型之间的向上向下类型转换.静态绑定 VS 动态绑定.某特性(函数)依赖与对象的静态类型,在编译期确定,某特性(函数)依赖于对象的动态类型,在运行期确定.只有通过基

C++ 虚继承和虚函数同时存在的对象模型

如果说没有虚函数的虚继承只是一个噩梦的话,那么这里就是真正的炼狱.这个C++中最复杂的继承层次在VC上的实现其实我没有完全理解,摸爬滚打了一番也算得出了微软的实现方法吧,至于一些刁钻的实现方式我也想不到什么理由来解释它,也只算是知其然不知其所以然吧. 分2个阶段来探讨: 1.      有虚函数的派生类虚继承了没有虚函数的基类: 2.      有虚函数的派生类虚继承了有虚函数的基类: 1.  基类无虚函数 1.1 虚.实基类都没有虚函数 这种情况也还算比较简单.因为虚函数表指针一定是会放在最开