首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > C++ >

强大的C++——把乘法变成加法解决方案

2012-03-17 
强大的C++——把乘法变成加法把乘法变成加法不要误会,不是用加法重载operator*。(做这种事情的程序员应该立刻

强大的C++——把乘法变成加法
把乘法变成加法

不要误会,不是用加法重载operator*。(做这种事情的程序员应该立刻开除)。或者任何跟计算有关的事。这里要讲的是另外一个故事。
当你看我这篇帖子的时候,是否想过你的计算机是如何构成的?内存、主板、硬盘、cpu、显卡、显示器、光驱、键盘、鼠标等等。没错,你肯定很熟悉了。那么,你是否想过电脑厂商为了生产不同的配置的计算机,准备了多少配件吗?不好意思,我也不清楚。不过没关系,我们可以假设。假设内存规格有256、512、1G、2G四种规格(不考虑牌号,后面也一样);硬盘规格有80G、100G、120G、160G和200G五种规格;显卡有三种(假设一下,我搞不清现在有多少种显卡);cpu有五款;显示器有4种;光驱有5种;鼠标键盘就不考虑了。
那么我们总共可以得到多少种配置呢?很简单,4*5*3*5*4*5=6000种!当然没有哪个厂商会推出6000款型号,只是假设一下。那么总共有多少配件呢?4+5+3+5+4+5=26种。也就是厂商只需管理26种配件,便可以制造出6000个机型。
现在让我们再假设一下,当初IBM发明PC的时候,一时糊涂,没有把PC的各个组成部分组件化,所有的组成部分都是焊在一块电路板上的,包括显示器。那么如果一个厂商想获得这6000种配置的电脑,那么他们就必须直接生产,并且管理6000种不同的组件(电脑)。
这就是差别,组件化vs非组件化:26对6000。
好,现在回到我们熟悉的软件(开发)上来。我们在软件开发是通常也面临计算机厂商同样的问题:产品多样性的问题。即便是同一种软件,在不同的客户那里通常会有不同要求。为每一个客户开发不同的软件,明显是非常低效的。(不幸的是,这种愚蠢的行为,在业界几乎成了惯例)。顺便说明一下,这里的客户是指用你软件的人。如果你开发的是库,那么客户就是使用你的库的人。而本文主要针对的是库开发这种情况。
假设,我们要开发一个数据库访问的包装库。为其它程序员提供方便快捷地访问数据库的能力,使他们免于和难缠的ODBC或OleDB打交道。但是,前提是我们的包装库不能像ADO.net那样折损开发人员的访问能力。
基于这种前提,我们需要考虑数据库访问的几个基本要素。我归纳了一下,大概可以包括这么几个:游标、数据绑定、数据缓冲。为了简化,其他细枝末节暂不考虑。同时,只考虑结果集处理部分。下面,我们将考察两种不同的实现方式:OOP和GP。
先看OOP方式。OOP方式利用多态和后期绑定提供了扩展能力和一致的接口。代码大概会是这样:
class   MyDBAccessor  
{

virtual   bool   MoveNext();
virtual   bool   MovePre();
virtual   bool   MoveFirst();
virtual   bool   MoveLast();

virtual   bool   GetData(const   string&   field,   void**   data);
virtual   bool   GetData(int   field,   void**   data);
virtual   bool   SetData(const   string&   field,   void*   data)   {…}
virtual   bool   SetData(int   field,   void*   data)   {…}

virtual   void   BindColumn(const   string&   field,   DBType   type);
virtual   void   BindColumn(int   field,   DBType   type);

private:
virtual   void   DefaultBind()=0;

};
因为这里只关心程序的结构,所以只给出声明,略去定义。MyDBAccessor定义了一个基本的框架,对于不同的特性支持,比如不同的游标等等,通过在继承类中重载相应的虚函数实现:
class   MyDBFFAccessor//Fast-forward游标类型
:   public   MyDBAccessor
{

virtual   bool   MoveNext(){…}
virtual   bool   MovePre(){…}
virtual   bool   MoveFirst(){…}
virtual   bool   MoveLast(){…}

};
那么,当我们需要一个支持Fast-forward游标,自动绑定,并且按行缓冲的数据库访问类时,我们定义了如下的类:
class   MyDB_FF_Dyn_Row
:   public   MyDBAccessor
{

virtual   bool   MoveNext(){…}
virtual   bool   MovePre(){…}
virtual   bool   MoveFirst(){…}
virtual   bool   MoveLast(){…}

virtual   bool   GetData(const   string&   field,   void**   data)   {…}
virtual   bool   GetData(int   field,   void**   data)   {…}
virtual   bool   SetData(const   string&   field,   void*   data)   {…}
virtual   bool   SetData(int   field,   void*   data)   {…}

private:
virtual   void   DefaultBind(){…}

};
如果我们需要一个支持Dynamic游标,字符串绑定(所有类型转换成字符串),块缓冲的数据库访问类,那么就再定义一个继承类。
问题来了,游标类型至少有8种,假设默认绑定方式有5种(自动、宽/窄字符串绑定、xml绑定、手工绑定),数据缓冲方式有3种(行缓冲、块缓冲、数组缓冲)。
那么我们得定义多少个继承类呢?8*5*3+1=121。Mission   Impossible,除非你有ms那样的资源。
现在,我们来看看GP(范型编程)方式会不会好一些。我们先定义一个类模板:
template <class   Cursor,   class   Binder,   class   RowBuffer>
class   MyDBAccessor
:   public   Cursor,   public   Binder,   public   RowBuffer
{

};
应该看出来了吧,模板MyDBAccessor继承自模板类型参数Cursor、Binder、RowBuffer。而这三个模板参数分别对应了游标管理类、绑定类和行缓冲类。根据前面的假设,我们定义了8种游标管理类:


class   FastForwardCursor
{
public:
bool   MoveNext();
bool   MoveLast();
bool   GetData(const   string&   field,   void**   data);
bool   GetData(int   field,   void**   data);
bool   SetData(const   string&   field,   void*   data)   {…}
bool   SetData(int   field,   void*   data)   {…}
};
class   FastForwardReadOnlyCursor
{
public:
bool   MoveNext();
bool   MoveLast();
bool   GetData(const   string&   field,   void**   data);
bool   GetData(int   field,   void**   data);
};

class   DynamicCursor
{
public;
bool   MoveNext();
bool   MovePre();
bool   MoveLast();
bool   MoveFirst();
bool   GetData(const   string&   field,   void**   data);
bool   GetData(int   field,   void**   data);
bool   SetData(const   string&   field,   void*   data)   {…}
bool   SetData(int   field,   void*   data)   {…}
};
细心的人会发现这些游标管理类的接口(成员声明)都不一样,一会儿会告诉你为什么。
其他的绑定类和数据缓冲类都依次定义。当我们需要一个支持Fast-forward游标,自动绑定,并且按行缓冲的数据库访问类时,只需用相应的类实例化模板即可:
MyDBAccessor <FastForwardCursor,   DynamicBinder,   SingleRowBuffer> da;

如果我们需要一个支持Dynamic游标,字符串绑定(所有类型转换成字符串),快缓冲的数据库访问类,也很方便:
MyDBAccessor <DynamicCursor,   StringBinder,   BulkBuffer> da;

在GP方式中,我们只需定义8+5+3+1=17个类和模板,即可实现OOP方式中121类定义才能达到的效果。
非常好吧。还不止于此。假设你希望用DynamicCursor游标访问数据库,但是写错了变成了这样:
MyDBAccessor <FastForwardCursor,   DynamicBinder,   SingleRowBuffer> da;

da.MoveFirst();//啊呀!
不要告诉我你不会犯这种低级错误。这种错误每时每刻都在发生,最可能的一种情况就是软件的设计改了,由fast-forward游标改成dynamic游标,而你却忘了修改da的声明。
此时,代码不会编译通过。因为MyDBAccessor继承自游标管理类,并从游标管理类继承了操纵游标的成员函数。于是,根据fast-forward的定义:只进不退,所以没有MoveFirst()函数(也不需要,对吧)。因此,MyDBAccessor <FastForwardCursor,   DynamicBinder,   SingleRowBuffer> 也没有这个函数。那么da.MoveFirst()便会引发编译错误。
很多初学者可能不喜欢这种设计,因为他们非常害怕编译器错误。就好像编译器是他们中学语文老师一样。其实,你应该感谢这个编译错误,它在第一时间帮你消除了一个潜在的bug。如果我们在FastForwardCursor中加上MoveFirst()这个函数,编译自然没有问题。但在运行时,这句代码肯定会引发一个运行时错误。运气好的话在你测试的时候,运气不好的话可能会在你客户心情最差的时候发生。这个后果,…,哎呀呀。
另外,即使在你测试的时候发生,你也会被迫用几十上百个单步追查问题的原因,以至于把你周末约会女朋友的心情都搞坏了。&#61514;
好了,乘法变加法的把戏变完了。简单地讲,就是你可以利用模板、继承模板参数,以及多继承等技术,将一些基本的要素组合起来,构成一个复杂的,功能完整的类。用最少的代码作最多的事。这种技术是由C++领域的先锋官Andrei   Alexandresu提出来的,称为policy。更详细的内容,可以参考他的《Modren   C++   Design》,里面有很详细的讲解和案例。不过得做好心理准备,接受大剂量模板。


[解决办法]
GP而言,我觉得Policy也属于接口设计,关键的问题还是在于抽象,否则就需要很多特化,缺乏弹性,比如的例子里面:
----------------------------------------
非常好吧。还不止于此。假设你希望用DynamicCursor游标访问数据库,但是写错了变成了这样:
MyDBAccessor <FastForwardCursor, DynamicBinder, SingleRowBuffer> da;

da.MoveFirst();//啊呀!
不要告诉我你不会犯这种低级错误。这种错误每时每刻都在发生,最可能的一种情况就是软件的设计改了,由fast-forward游标改成dynamic游标,而你却忘了修改da的声明。
------------------------------------------
不能这样调用:da.MoveFirst();
我觉得一般来讲是增加一层来消除这种问题,比如加入iterator来达到。

不过还不错,呵呵
trait & Policy, 是好东西

up
[解决办法]
GP而言,我觉得Policy也属于接口设计,关键的问题还是在于抽象,否则就需要很多特化,缺乏弹性,比如的例子里面:
----------------------------------------
非常好吧。还不止于此。假设你希望用DynamicCursor游标访问数据库,但是写错了变成了这样:
MyDBAccessor <FastForwardCursor, DynamicBinder, SingleRowBuffer> da;

da.MoveFirst();//啊呀!
不要告诉我你不会犯这种低级错误。这种错误每时每刻都在发生,最可能的一种情况就是软件的设计改了,由fast-forward游标改成dynamic游标,而你却忘了修改da的声明。


------------------------------------------
不能这样调用:da.MoveFirst();
我觉得一般来讲是增加一层来消除这种问题,比如加入iterator来达到。

不过还不错,呵呵
trait & Policy, 是好东西

up


这个问题可以通过编译时检测来避免.方法很多,例如使用boost库的BOOST_STATIC_ASSERT检测该模板参数是否为所需要的类型.
[解决办法]
如果使用动态序列表管理,你可以通过修改参数文件的方式随时进行修改,而结果远比采用模版方式丰富得多,而这种参数化编程,无论是C还是C++都很容易做到,用模板却存在再编译的问题,这对分发设计、自动升级并不是很好的选择。当然,你可以向客户提供编译环境、源码,不过技术也就泄露了不是吗?
[解决办法]
泛型编程也就是GP(Generic Programming),推崇与类型无关的编程思想。简单来说,就是将针对不同数据类型的相同算法从数据结构 <类> 中剥离出来,
这与传统的C++的继承和多态截然不同。
可以说泛型思想中没有多少继承和多态的成分。甚至有背道而驰的味道。

在C++实现上,泛型严重依赖于模板机制,事实上如果没有模板的引入,泛型绝对不会在C++语言中获得如此巨大的成功!

泛型代码由C++编译器在编译期解释成二进制代码。也就是说,泛型的代码编译起来可能会使程序的二进制代码变大,但这一点也保证了程序在运行的时候不会损失效率...
当然泛型不是万能的,滥用泛型也许一样会使你的代码缓慢...
工具毕竟还是工具,思想和方法总有其局限性的。
如果没接触过,学习泛型编程,对你是一个全新的体验 。

热点排行