⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 天方夜谭vcl:多态.htm

📁 csdn10年中间经典帖子
💻 HTM
📖 第 1 页 / 共 2 页
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<!-- saved from url=(0047)http://www.c-view.org/journal/004/vcl_chong.htm -->
<HTML><HEAD><TITLE>天方夜谭VCL:多态</TITLE>
<META content="text/html; charset=gb2312" http-equiv=Content-Type><LINK 
href="天方夜谭VCL:多态.files/style.css" rel=stylesheet type=text/css>
<META content="MSHTML 5.00.3315.2870" name=GENERATOR></HEAD>
<BODY>
<SCRIPT src="天方夜谭VCL:多态.files/header.js"></SCRIPT>

<CENTER>
<H3><A href="http://www.c-view.org/tech/framework/vcl_chong.htm">天方夜谭VCL</A>: 
多态</H3>虫虫<BR></CENTER>
<P>我们中国人崇拜龙,所谓“龙生九种,九种各别”。哪九种?《西游记》里西海龙王对孙悟空说:“第一个小黄龙,见居淮渎;第二个小骊龙,见住济渎;第三个青背龙,占了江渎;第四个赤髯龙,镇守河渎;第五个徒劳龙,与佛祖司钟;第六个稳兽龙,与神官镇脊;第七个敬仲龙,与玉帝守擎天华表;第八个蜃龙,在大家兄处砥据太岳。此乃第九个鼍龙,因年幼无甚执事,自旧年才着他居黑水河养性,待成名,别迁调用,谁知他不遵吾旨,冲撞大圣也。”(注:鼍龙是文雅的说法,民间叫法是猪婆龙,也就是扬子鳄。)如果您冲着这九位说一声“Let’s 
go”,那场面可壮观了,有天上飞的,有水里游的,也有地上爬的。同样是“go”,“go”的具体形式却各不相同,这正是多态“一个接口,多种实现”的典型例子。 
<P>多态的实现方法很多,其中C++直接支持的方式有:通过关键字virtual提供虚函数进行迟后联编,以及通过模板(template)实现静态多态性,它们都各有用武之地。我们比较熟悉的是虚函数,这是建构类层次的重要手段,我们也已经分析过虚函数的原理[<A 
href="http://www.c-view.org/journal/004/vcl_chong.htm#11" 
name=1>1</A>]。然而在有些情况下,虚函数的性能并不是最优,故VCL还提供了一种动态(dynamic)函数,用法和虚函数一模一样,只要把virtual换成DYNAMIC就可以了。VCL的帮助文件里说,动态函数跟虚拟函数相比,空间效率占优,时间效率不行,真的吗?其实现原理又是如何呢?我们又应该如何权衡这两者的使用呢?我们将从一个相当一般的角度来讨论这些问题。 

<H5>虚函数的苦恼</H5>
<P>如下类层次来自一个图形绘制程序的一部分。为了方便管理,界面与具体的图形设计分离。各种图形以动态连接库的方式提供,作为插件的形式。这样可以在不重新编译主程序的情况,增加或减少各种图形。 

<P align=center><IMG src="天方夜谭VCL:多态.files/vcl01.gif"><BR>图1 Shape类层次 
<P>最初Shape的声明是 <PRE>class Shape {
private:
	int x0, y0;
protected:
        Shape();
        virtual ~Shape();
public:
	int x() const;
	int y() const;
        virtual void draw(void *) = 0;
        virtual int move(int, int);
};
</PRE>后来因为功能扩充,添加了两个虚函数。 <PRE>class Shape {
private:
	int x0, y0;
protected:
        Shape();
        virtual ~Shape();
public:
	int x() const;
	int y() const;
	virtual int move(int, int);
        virtual void draw(void *) = 0;
        <B>virtual void save(void *) const = 0;
        virtual void load(void *) = 0;</B>
};
</PRE>后来又作过一些修改,又添加了若干虚函数。问题就在于,虚函数一但增加,虚拟函数表VFT就会发生变化,这时候,主程序就必须重新编译。更糟糕的是,一旦版本升级,派生自不同版本Shape的图形绝对不可以混用[<A 
href="http://www.c-view.org/journal/004/vcl_chong.htm#22" 
name=2>2</A>]。所以我们可以看到硬盘里充斥着mfc20.dll、mfc40.dll、mfc42.dll……却一个也不能删除,这就是MFC升级所带来的DLL垃圾。怎么办? 

<H5>初步解决</H5>
<P>我在网上问过这样的问题,得到的答复主要有: 
<UL>
  <LI>用COM; 
  <LI>预先多写一些无用的虚函数,留出扩充空间。 </LI></UL>
<P>其实上面的方法都能很好地解决这个问题。但是推广看来,也有一定局限性。COM不适合解决类层次过深的情况,预留的空间则是不折不扣的“鸡肋”。 
<P>追根究底,这个局限性是因为父类和子类的虚拟函数表VFT之间过强的关联性:子类的VFT的前面一部分必须与父类相同。而当父类和子类不在同一个DLL或EXE中的时候,这个要求是很难满足的。父类一旦改变,子类如果不重新编译,就将导致错误。解决的方法,当然就是取消父类和子类VFT之间的关联性。我设计了一个很笨的解决办法,但可以取消这个关联性,使虚函数保证始终只有2个。 
<PRE>#define Dynamic // Dynamic什么都不是,只是好看一点

struct point
{
	int x, y;
};

class dispatch_error{};

class Shape {
private:
	int x0, y0;
protected:
        Shape();
        virtual ~Shape();
	virtual void dispatch(int id, void* in, void* out);
	// in和out是函数的输入输出参数,id是每个函数唯一的标记符号,即代号
	// 实际运用中,id不一定是整数,也可以是128位UUID,或者字符串等等
public:
	int x() const;
	int y() const;
	Dynamic int move(int dx, int dy)
	{
		int r;
		point p = {dx, dy};
		dispatch(-1, &amp;p, &amp;r);
		return r;
	}
        Dynamic void draw(void *hdc)		{dispatch(-2, hdc, 0);}
        Dynamic void save(void* o) const	{dispatch(-3, o, 0);}
        Dynamic void load(void* i)		{dispatch(-4, i, 0);}
};

void Shape::dispatch(int id, void* in, void* out)
{
        switch(id)
        {
                case -1:
                        ...
                case -2:
                        ...
		...
                default:
                        throw(dispatch_error()); // 若函数不存在则抛出异常
        }
}
</PRE>如果子类Triangle要改写Shape::draw,那么只需要 <PRE>void Triangle::dispatch(int id, void* in, void* out)
{
        switch(id)
        {
                ...
                case -2:	// 改写Shape::draw
                        ...
		...
                default:
                        Shape::dispatch(id, in, out); //函数不存在则向父类找
        }
}
</PRE>这样的“Dynamic函数”就解决了前面的问题,只有析构和dispatch这两个虚函数。父类和子类的VFT之间没有关联性,可以自由改动而不会互相影响。 

<H5>评头论足</H5>
<P>我们来对这种解决方案作了评价:的确解决了虚函数的问题,但是也付出了不小的代价:时间效率和可读性,由此也决定了该方案的应用面不广,一般用于 
<UL>
  <LI>虚函数很少或几乎不需改写的情况。这样有助于减少VFT的大小。至于运行速度则没有什么提高,毕竟VFT的访问速度是常数级[<A 
  href="http://www.c-view.org/journal/004/vcl_chong.htm#33" name=3>3</A>]; 
  <LI>父类需要经常更新而子类不方便同步更新,对效率要求又不高的情况。一般的应用程序都可以使用。 </LI></UL>
<P>从模式(Patterns)的角度来看,这种方法是典型的职责链(Chain of Responsibility)模式[<A 
href="http://www.c-view.org/journal/004/vcl_chong.htm#44" 
name=4>4</A>]:调用请求从最低层子类开始一层层往上传递,直到被处理或者最后抛出异常。这种模式运用非常广泛,比如VCL消息映射[<A 
href="http://www.c-view.org/journal/004/vcl_chong.htm#55" 
name=5>5</A>]和COM中IDispatch接口[<A 
href="http://www.c-view.org/journal/004/vcl_chong.htm#66" 
name=6>6</A>],与上述解决方案的形式都非常相似。 
<P>这个解决方案还可以作进一步的完善,以更好地适用于单根结构的框架。比如单根结构的类库,如MFC和VCL,通过RTTI可以找到唯一的父类,那么可以分离数据(函数代号和指针)和代码(调配部分),以简化结构。解决的方法就是典型的表格驱动,有不少书[<A 
href="http://www.c-view.org/journal/004/vcl_chong.htm#77" name=7>7</A>,<A 
href="http://www.c-view.org/journal/004/vcl_chong.htm#88" 
name=8>8</A>]都用此来优化COM中IUnkown接口的QueryInterface。我们引入类DMT来储存函数的代号和指针。 <PRE>#include <CSTDARG>
using namespace std;

class DMT {
        char* const ptr;
        const DMT* const parent;
public:
        DMT(const DMT* const, const int, ...);
        ~DMT() {delete []ptr;}
        short size() const  {return *(short*)ptr;}
        const void* find(int) const;
};
</PRE>
<P align=center><IMG src="天方夜谭VCL:多态.files/vcl02.gif"><BR>图2 类DMT图解 
<P>需要特别注意的是DMT::ptr所分配的空间。在32位系统上,对于n个“Dynamic函数”,需要sizeof(short)字节储存n(红色部分),sizeof(void*)*n字节储存函数代号(黄色部分),以及sizeof(void*)*n字节储存函数指针(蓝色部分),一共是sizeof(short) 
+ 2*n*sizeof(void*)字节。子类和父类的DMT可以通过链表形式连接起来。下面我们看看DMT::find和DMT::DMT的实现。 <PRE>const void* DMT::find(int i) const
{
	const int* begin = (int*)(ptr + sizeof(short)), *p;
	for(p = begin; p &lt; begin + size(); ++p)
		if(*(int*)p == i)
			return *(void**)(p+ size());
			// 找到对应的函数代号后,向前跳DMT::size()则是相应的函数指针
	return (parent)? parent-&gt;find(i): 0;
}

DMT::DMT(const DMT* const p, const int n, ...)
	: parent(p), ptr(new char[sizeof(short)+2*n*sizeof(void*)])
					// ptr分配的空间大小如前所述
{
	int* i = (int*)(ptr + 2), c;
	*(short*)ptr = n;		// 往头sizeof(short)字节写入n(红色部分)

	va_list ap;
	va_start(ap, n);

	for(c = 0; c &lt; n; ++c)		// 往黄色部分写入函数代号
		*(i++) = va_arg(ap, int);

	typedef void (DMT::*temp_type)();
	temp_type temp;

	for(c = 0; c &lt; n; ++c)		// 往蓝色部分写入函数指针
	{
		temp = va_arg(ap, temp_type);
		*(i++) = *(int*)&amp;temp;
	}

	va_end(ap);
}
</PRE>下面我们在Shape类层次中应用DMT类。 <PRE>class Shape {
private:
	int x0, y0;
<P style="BACKGROUND: #ccffcc"><CODE>	void int f_move(void* dx, void* dy) {...}</CODE></P>
protected:
<P style="BACKGROUND: #ccffcc"><CODE>	static const DMT dmt_Shape;			// Shape类的DMT
	const DMT* const dmt;				// 指向该类DMT的指针
	Shape() : dmt(&amp;dmt_Shape) {...}</CODE></P>
        virtual ~Shape();
<P style="BACKGROUND: #ccffcc"><CODE>	void dispatch(int id, void* in, void* out)	// 这次不是虚函数!
	{
		void (A::*f)(void*, void*);
		*(const void**)&amp;f = dmt-&gt;find(id);
		(this-&gt;*f)(in, out);
	}</CODE></P>
public:
	int x() const;
	int y() const;
	Dynamic int move(int dx, int dy)
	{

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -