`
helloyesyes
  • 浏览: 1304029 次
  • 性别: Icon_minigender_2
  • 来自: 武汉
文章分类
社区版块
存档分类
最新评论

被误解的C++——螺蛳壳里做道场

阅读更多

螺蛳壳里做道场

“螺蛳壳里做道场”是我们那里的一句俗话,意思是在很受限制、充满约束的情况下,做一些复杂的事情。前段时间我就遇到这么一个问题。

经常开发MIS类的应用,不免需要和数据库打交道。一直使用VC,(唉,反正我们公司已经下了ms这条贼船了),访问数据库无非就是ODBCDAOOleDBADO什么的。(ADO.net因为无法控制服务端游标,被我一脚踢出候选名单)。

我是个懒人。ODBCOleDB。虽然功能强大,使用灵活,可一大堆Workaround,让我直起鸡皮疙瘩。所以,我打算选用一个直接可用的类库,帮助我简化开发。MFCCResoultSet什么的太臃肿了,没入我的法眼。这样,就只能选择OleDB Template了。OleDB Template倒是不错,policy化,使用起来既方便,又高效。其方便程度和ADO相差无几。

唉,我也是一个爱多事的人。有这么个不错的库,用便是了,可我却觉得不满意。问题主要集中在三个方面:

<!--[if !supportLists]-->1. <!--[endif]-->policy划分不合理,缺少游标控制、读写控制和书签控制的policy

<!--[if !supportLists]-->2. <!--[endif]-->policy实现也未能充分发挥静态类型约束的优点;

<!--[if !supportLists]-->3. <!--[endif]-->只能输出原始类型的数据,不能转换输出。

针对这三个方面,我打算改造OleDB Template。当然我也可以重做一个库,但是这不符合我懒人的风格。于是,我决定通过扩展OleDB模板和类达到这个目的。

我最主要的改造对象是CCommand模板。这个模板的声明如下:

template<
classTAccessor=CNoAccessor,
template
<typenameT>classTRowset=CRowset,
classTMultiple=CNoMultipleResults
>
classCCommand;

这里,TAccessor是用于访问控制的policy,负责数据绑定和输入输出,包括CManulAccessor(手工绑定数据,一般很少使用)、CDynamicAccessor(“全自动”数据绑定,使用最方便的Accessor)、CDynamicParameterAccessor(在CDynamicAccessor基础上,增加参数功能)、CStringDynamicAccessor(以string绑定所有类型,通常用于数据显示)、CXMLAccessor(输出XML格式数据的Accessor)等等。

TRowset是用于管理数据缓存和游标运动的policy,包括CRowset(普通rowset)、CBulkRowset(块操作rowset)、CArrayRowset(提供[]操作符,可以像操作数组一样操作结果集)。

TMultiple是负责控制多结果集返回的policy

作为改造,我首先需要增加三个新的policy

<!--[if !supportLists]--> 1. <!--[endif]-->CursorT,实现游标控制。包括defaultfast-forwardstatickey-setdynamic五种游标;

<!--[if !supportLists]-->2. <!--[endif]-->ReadWriteT,实现读写控制。包括read-onlyread-write

<!--[if !supportLists]-->3. <!--[endif]-->BookMarkT,实现书签管理。包括has-bookmarkno-bookmark

同时,还需要为新的Command模板加上静态约束。也就是,如果CursorTdefaultfast-forward两种只进游标,那么Command模板的实例(类)只提供MoveNext()成员函数;如果是其他服务端游标,则提供MovePre()MoveFirst()MoveLast()等等成员函数。如果ReadWriteTread-only,那么只提供GetValue()成员,否则,必须提供SetValue()UpdateData()等成员。BookMarkT也有类似的情况。

这么做,可以使得一些对游标、读写等错误的使用在编译期就得到拦截,而无需象OleDB Template那样等到运行期检验返回结果码才能知道。(其实,我也可以通过改造所有的PolicyCCommand模板,使用异常代替结果码。但是工程量太大,暂时不做考虑)。

另外,我还需要增加一个policyConvertorT,负责类型转换,确保数据输出的类型安全。同时也简化操作。(不再需要读取数据类型,再将数据读出到相应类型的数据缓冲区中)。包括AutoConvertorT,执行自动类型转换。和NoConvertorT,不执行转换,用于性能要求很高的情况。将来,如果需要,还可以开发PartialConvertorT,只执行与字段类型相对应的C++类型的转换。

所有这些扩展,都无法在原始的OleDB Template中实现,因为我无权修改OleDB Template的源代码。(这就叫螺蛳壳)。于是,我作了一个扩展库,用新的模板实现我需要的功能,但其实现,还是使用老的OleDB Template

template<
typenameBinderT,
typenameBufferT,
typenameCursorT,
typenameReadWriteT,
typenameBookMarkT,
typenameConvertorT,
typenameMultiResultT
>
classCommandExt:
publicBinderT,
publicBufferT,
publicCursorT,
publicReadWriteT,
publicBookMarkT,
publicConvertorT
...{

private:
typedefBinderT::AccessorTAccessorT;
typedefBufferT::RowsetTRowsetT;
typedefCCommand
<AccessorT,RowsetT,MultiResultT>CommandT;

CommandTm_Command;
}
;

Ext或者Ex可是个很有用的词根,凡是需要扩展的地方,都可以用。有时甚至可以几个连在一起用。呵呵,都是从ms那里学来的。当然啦,如果原来设计得好,扩展性强,那么也就用不着Ex或者Ext了,对吧?

这个模板的实现,原先我是打算让CommandExt继承自这些Policy,这是标准的现代Policy模式。这里,BinderT policy提供了类型AccessorT,在不同的Binder里,定义了不同的AccessorT别名。比如AutoBinderT里,就可以用CDynamicAccessor定义AccessorT。同样,不同的BufferT里,RowsetT也定义了相应的Rowset模板。此后,CCommand<>模板在CommandExt<>里实例化,并且创建一个成员对象。这是Composite模式标准应用。

但是,我立刻遇到了麻烦。因为所有的policy都需要操作m_Command对象。对此,我有两个办法:其一,让每一个policy持有m_Command的引用或指针。其二,通过call-back,让policy回调CommandExt的事件,将所需的操作交由CommandExt处理。

结果我发现,两者都有问题。当我草草写下这样一个构造函数时,并未意识到事态的严重:

CommandExt():
BinderT(m_Command),
BufferT(m_Command),
CursorT(m_Command),
ReadWriteT(m_Command),
BookMarkT(m_Command)
...{}

这里有两个问题。第一,我打算让policy持有m_Command的引用,所以只能通过其构造函数对其初始化。(我不会用指针的,因为实在不想每次操作前都对指针做有效性检验,太费事,也太低效了)。但是m_Command,是CommandExt的成员变量,是在所作为基类的policy初始化完成之后再初始化。这里的初始化方式是“违反规定”的。但是,在这里的特殊情况下,这不会成为问题。因为在policy的构造函数中,我并未“使用”m_Command,仅仅用它初始化了一个引用。m_Command的内存是在CommandExt()之前就已经分配完成的(同CommandExt本身一起),所以仅仅取得m_Command地址是不会有问题的。事实也证明,我的这个“冒险”举动并未产生任何问题,除了编译器的几声微弱的抱怨(warning)。

第二个问题就严重了,是致命的。当我试图写policy的构造函数时,便迎头撞上了这个问题:

classAutoBinder
{
public:
AutoBinder(
???cmd):m_rCommand(cmd){};

private:
???m_rCommand;
};

标记为???的地方填什么?也就是这里放什么类型。请注意AutoBinderpolicy定义于CommandExt使用之前,根本无法知道最终m_Command的类型是什么。

一种明显的解决方法是把所有policy做成模板:

template<typenameA,typenameR,typenameM>
classAutoBinder
...{
public:
typedefCommand
<A,R,M>CommandT;
public:
AutoBinder(CommandTcmd):m_rCommand(cmd)
...{};

private:
CommandTm_rCommand;
}
;

这种解决方法不但累赘,而且使得policy这些本该相互正交的类型耦合在了一起。

另一种可能(只是可能)的方法,就是使用C++0xauto

classAutoBinder
...{
public:
AutoBinder(autocmd):m_rCommand(cmd)
...{};

private:
autom_rCommand;
}
;

限于对auto提案的了解,我并不知道这个方案是否真的可行。(哪位知晓,请告知,先谢了J)。况且远水解不了近渴。

所以,我转而采用回调方案。但是,很快我遇到了新的问题:在CommandExt中定义的事件是“死的”,而policy上的功能是独立的、自由的。在CommandExt中很难预测到policy对回调的需求。

任何方案都有问题,我陷入了进退两难的境地。整整两天,我都陷在这个问题中,不能自拔。我并没有放弃,因为我有一种感觉,一个非常明显的解决方案唾手可得,只是没有发现而已。

经过长期的工作,我养成了一个习惯:如果解决不了一个问题,就反过来想想。所以,我就尝试着从另一个方向分析这个问题:我希望增加一些policy,并且通过改变policy的实现强化静态约束。静态约束也就是限制CommandExt的行为。。嗯?限制行为?一个模板/类的行为是什么?。没错,是它的成员函数。限制成员函数?不就是去掉几个成员函数么。去掉成员函数?我知道C++0x里有=delete,可以去掉某个特殊成员函数。可是,不能去掉普通成员函数。(唉,即便行,远水也解不了近渴)。

突然,我想起了Bjarne的话:如果你不想要什么,就把它声明成private!(D&E

我有办法了!但是吃不准是否能行。于是,等到儿子跑开,去粘着他妈妈的时候,我立刻打开计算机,做了个试验程序,看看继承类的private成员函数是否能够屏蔽掉基类中同名的成员函数。我想很多人都知道结果吧:可行!

所以,第二天,我便设计了一个新方案,只用了半天的时间,便大致构造完成了这个扩展库。新的CommandExt模板是这样定义的:

template<
typenameAccessorT,
template
<typenameA>classRowsetT=CRowset,
template
<typenameR>classCursorT=DefaultCursor,
typenameReadWriteT
=ReadOnly,
typenameBookMarkT
=NoBookMark,
template
<typenameA>classConvertorT=AutoConvertor,
typenameMultiResultT
=CMultipleResults
>
classCommandExt
:
publicCCommand<
typenameAccessorExt
<
AccessorT,ReadWriteT,
BookMarkT,ConvertorT>::Type,
typenameRowsetExt<
RowsetT,CursorT,ReadWriteT,
BookMarkT>
::Type,
MultiResultT
>
{…};

比较复杂,我一点一点解释。头两个和最后一个模板参数和CCommand的一样。中间的都是新增的。CursorTConvertorT都是template-template parameter,而ReadWriteTBookMarkT都是类型。这种差异是有原因的,(当然是迫不得已),一会儿会看到。

让我们先看看新的policy是如何工作的。以CursorT为例,我给出两个不同的Cursor Policy,一看就明白了:

//FastForwardCursorPolicy
template<typenameRowsetT>
classFastForward:publicRowsetT
{
private
HRESULTMoveFirst();
HRESULTMoveLast();
HRESULTMovePrev();
HRESULTMoveToRatio();
};
//scrollableCursorPolicy
template<typenameRowsetT>
classStaticCursor:publicRowsetT
{
};

FastForward声明了MoveFirst()等四个成员函数,并将其置为private。而StaticCursor没有声明任何成员函数。这些Policypublic继承自模板参数,于是,private成员函数将会屏蔽所有基类的同名成员函数。由于类型参数RowsetT必然是一个CRowset类或其继承类。所以,FastForward将会屏蔽RowsetT上相应的成员函数,而只剩下MoveNext()。这样,便使CommandExt具备了与游标类型相一致的行为(只进不退)。相反,StaticCursor没有定义任何成员函数,便不会覆盖RowsetT上的任何成员函数。于是,CommandExt则具备了static游标相一致的行为(可进可退,随机访问)。

其他的各个policy都采用这种形式来“修饰”相应的OleDB Template类。实际上,这种方式就是Matthew Wilson的“饰面”(Imperfect C++, 21章)的一种特殊应用。

接下来,再看看CommandExt的基类。CommandExt直接继承自CCommand,只是类型实参用了更加炫目的形式给出。在解释这一大堆杂碎之前,先了解一下CCommand同它的类型参数之间的关系:

template<>
classCCommand:
publicCAccessorRowset<
TAccessor,
TRowset
>,
publicCCommandBase,
publicTMultiple
{…}

CCommand继承自三个类:CAccessorRowset< TAccessor, TRowset >CCommandBaseTMultipleCCommandBase包含了CCommand的一些基础服务,同我们的主体无关。TMultiple是类型参数,负责控制多结果集,它工作的很好,也就不去理它了。剩下的,就是CAccessorRowset<>模板了。看一下它的定义,我们便明白该从何入手解决问题了:

template<
classTAccessor=CNoAccessor,
template
<typenameT>classTRowset=CRowset
>
classCAccessorRowset:
publicTAccessor,
publicTRowset<TAccessor>
...{}

该模板继承自它的两个模板参数TAccessorTRowset,而且都是public的。(说明一下,TRowset<>并没有继承自它的类型参数,类型参数TAccessor有其他用途)。这表明一点,CCommand本身并不提供数据绑定、缓存处理、数据输出等操作。这些工作都是由TAccessorTRowset完成的。这就是现代policy模式。

基于这个结构,我们可以很容易地想到,只要把TAccessorTRowset用我们新增的policy约束起来,便可以达成目的。说做就做,我把CommandExt的定义改成如下结构:

template<
typenameAccessorT,
template
<typenameA>classRowsetT=CRowset,
template
<typenameR>classCursorT=DefaultCursor,
template
<typenameR>classReadWriteT=ReadOnly,
template
<typenameR>classBookMarkT=NoBookMark,
template
<typenameA>classConvertorT=AutoConvertor,
typenameMultiResultT
=CMultipleResults
>
classCommandExt
:
publicCCommand<
BookMarkT
<ReadWriteT<ConvertorT<AccessorT>>>,
BookMarkT<ReadWriteT<CursorT<RowsetT<AccessorT>>>>
,
MultiResultT
>
{…};

看起来很合理,但是这里存在两个问题。(此时我才意识到,OleDB Template这个“螺蛳壳”有多小)。第一,BookMarkTReadWriteT在两个模板参数中都出现。而这两个模板参数最终都为CommandExt所继承(并且都是非virtual继承,我们也改不了),这将会引爆多继承上的核心问题(两组嵌套的policy分别操纵不同的子对象)。尽管目前BookMarkTReadWriteT对应的类都是空的,但是我们不能确定未来扩展都能提供这种保证。

造成这个问题的原因,主要是AccessorTRowsetT,也就是OleDB TemplateAccessor类和Rowset类,都拥有与读写和书签相关的成员函数。比如Get/SetValueAccessor上,而UpdateDataRowset上。这样,我们被迫对这两组类都必须同时施加读写和书签policy

第二个问题,CCommand的第二个模板参数应当是一个模板,但BookMarkT<ReadWriteT<CursorT<RowsetT<AccessorT> > > >是一个类。无法通过编译。

对于第一个问题,经过一番考虑,我想出了这样一个办法:分别针对AccessorRowset制作读写和书签policy,并把它们包装在一个类中:

structReadOnly
{
//用于约束Rowset的部分
template<typenameRowsetT>
classForRowset:publicRowsetT
{
private:
HRESULTDelete();
HRESULTInsert();
HRESULTSetData();
HRESULTUndo();
HRESULTUpdate();
HRESULTUpdateAll();
};
//用于约束Accessor的部分
template<typenameAccessorT>
classForAccessor:publicAccessorT
{
private:
boolSetLength();
boolSetStatus();
boolSetValue();
};
//CManualAccessor有其特殊性,对其特化,不做约束
template<>classForAccessor<CManualAccessor>
:
publicCManualAccessor{};
};

这样,可以把CommandExt继承类改成:

:publicCCommand<
typenameBookMarkT::ForAccessor
<
typenameReadWriteT::ForAccessor
<
ConvertorT
<AccessorT>>>,
typenameBookMarkT::ForRowset
<
typenameReadWriteT::ForRowset
<
CursorT
<RowsetT<AccessorT>>>>,
MultiResultT
>

但这依然无法通过编译,因为同样存在第二个问题。为解决这个问题,我做了一个辅助模板:

template<
template
<typenameA>classRowsetT,
template
<typenameR>classCursorT,
typenameReadWriteT,
typenameBookMarkT
>
structRowsetExt
{
template
<typenameA>
classType
:
publicReadWriteT::ForRowset<
typenameBookMarkT::ForRowset<
CursorT<RowsetT<A>>>>

{};
};

这里,类模板的基类就是前面CCommand的第二个模板实参。这样,便构造出一个符合Rwoset要求的模板。其实,最完美的方案是使用C++0xtemplate alias(也就是template typedef):

template<>
structRowsetExt
{
template
<typenameA>
usingType=ReadWriteT::ForRowset<
typenameBookMarkT::ForRowset<
CursorT<RowsetT<A>>>>
;
};

当然啦,目前我们也只能将就了。为了保持形式统一,我也为Accessor做了一个辅助模板:

template<
typenameAccessorT,
typenameReadWriteT,
typenameBookMarkT,
template
<typenameA>classConvertorT
>
structAccessorExt
{
classType
:
publicReadWriteT::ForAccessor<
typenameBookMarkT::ForAccessor
<
ConvertorT
<AccessorT>>>
{};
分享到:
评论

相关推荐

    煤炭行业2020年度投资策略:螺蛳壳里摆“道场”,两条主线来穿越-20191126-光大证券-25页.pdf

    这两条主线被认为是穿越行业波动,寻找稳定收益的重要途径。 首先,煤价弹性将持续收窄。2020年是供给侧改革的关键阶段,政策重心由“去总量”转向“调结构”。这预示着煤炭行业的产能调整将更加精细,供需关系将...

    互联网环境下农产品区域公用品牌建设的影响因素研究——以广西柳州螺蛳粉为例.pdf

    以广西柳州螺蛳粉为例,通过网络平台的推广,螺蛳粉从地方特色美食逐渐演变成为全国皆知的“网红”食品,并且实现了产业化发展。在农产品区域品牌建设中,互联网发挥了巨大的作用,主要体现在以下几个方面: 首先,...

    2021螺蛳粉行业发展白皮书.pdf

    2021螺蛳粉行业发展白皮书.pdf 本报告是关于2021螺蛳粉行业发展的白皮书,涵盖了螺蛳粉行业的发展趋势、消费趋势、产品深耕、市场分析等多个方面。报告首先对螺蛳粉行业的发展趋势进行了分析,表明了螺蛳粉消费人数...

    2020柳州电商及螺蛳粉行业线上市场研究报告.pdf

    "2020柳州电商及螺蛳粉行业线上市场研究报告.pdf" 柳州电商市场分析 柳州电商市场的整体规模和增长情况显示,柳州淘宝天猫平台线上销售额呈波动性增长趋势,2019.4-2020.3近1年销售额达22.7亿元,尤其是2020年2、3...

    静安区2004学年第一学期期末检测高三语文[精选].doc

    3. “螺蛳壳里做道场”是上海方言,比喻在极其有限的空间里做出一番事业或者布置得很精美。在这篇文章中,它用来形容上海人如何巧妙地利用狭小的亭子间,使其成为精致的生活或工作空间,体现了上海人的智慧和创造力...

    螺蛳粉商业计划书_商业计划 策划书 经营方案.ppt

    项目选择了餐饮业作为切入点,特别是广西柳州的特色小吃——螺蛳粉,旨在打造东莞的螺蛳粉精品。计划书将提供全面的评估和行动指导,以便进行风险投资。 二、产品及服务 项目的核心产品是螺蛳粉,一种结合酸、辣、...

    《第一财经》:螺蛳粉火起来靠什么.pdf

    最后,螺蛳粉产业要想持续做为网红产品,实现长远发展,还需要在政策扶持、资本加持、产业升级、渠道助推、消费群体拓展这五个环节上不断努力,使其能够环环相扣、互融共通。面对激烈的市场竞争和不断变化的消费需求...

    螺蛳粉投资商业计划书.zip

    《螺蛳粉投资商业计划书》是一份详细阐述螺蛳粉产业投资的文档,旨在为潜在投资者提供全面的行业分析、市场前景、经营策略及财务预测。这份商业计划书不仅揭示了螺蛳粉这一美食的独特魅力,同时也展示了其在餐饮市场...

    螺蛳粉投资商业计划书.ppt

    【螺蛳粉投资商业计划书】是一份详细阐述在东莞开设螺蛳粉餐饮店的商业策划,旨在通过投资实现盈利并打造柳州螺蛳粉的品牌。以下是该计划书的关键点: 1. **项目概述**:强调了创业的风险与回报,选择餐饮业作为...

    高效率测试之巧用策略模式

    做过测试工作的人或许都知道,...所幸的是,微软的工程师们总能绞尽脑汁,螺蛳壳里做道场,榨干每一行代码、每一个测试用例的价值,最终把“杯具”变成“洗具”。下面就给大家讲一个我参与的项目中利用策略模式(Strat

    创业计划书-螺蛳粉商业计划书

    《螺蛳粉商业计划书》是一份详细的创业指导文档,主要针对想要在餐饮行业中开设螺蛳粉店的企业家。这份计划书涵盖了多个关键领域,包括市场分析、产品定位、运营策略、财务预算以及风险评估,旨在为创业者提供全面的...

    螺蛳粉商业计划书.ppt

    【螺蛳粉商业计划书】是一份详细的创业投资项目文档,主要涵盖了从项目概述到财务预算等多个关键环节。项目的核心是开设一家以螺蛳粉为主打产品的餐饮店,旨在利用餐饮行业的消费潜力,尤其是针对广东、广西等地喜好...

    创业计划书-螺蛳粉投资商业计划书

    《螺蛳粉投资商业计划书》是一份详细阐述如何在螺蛳粉行业中开展创业和投资的文档,通常包括市场分析、产品介绍、营销策略、运营规划、财务预算以及风险评估等多个方面。以下是对这份计划书可能包含的关键知识点的...

    螺蛳粉投资商业计划书.pdf

    螺蛳粉投资商业计划书.pdf

    基于BP神经网络算法下网红食品柳州螺蛳粉的影响分析与销量预测.pdf

    该文主要探讨了基于BP神经网络算法对网红食品——柳州螺蛳粉的销量预测和影响因素分析。文章指出,柳州螺蛳粉作为网红食品,其市场表现受到多种因素影响,包括价格、库存、销售数据、用户评价等。作者通过爬取淘宝网...

    商业计划书案例-螺蛳粉商业计划书.pptx

    - **项目定位**:项目旨在打造一款独具特色的螺蛳粉品牌——“东莞螺蛳粉”,强调其独一无二的口味体验。 ### 二、项目产品及服务 - **主要产品**:螺蛳粉作为主打产品,以其独特的酸、辣、鲜、爽、烫风味著称。...

    螺蛳室内养殖技术,福寿螺和田螺有什么区别.doc

    螺蛳室内养殖技术是一项经济高效的水产养殖方法,尤其适合家庭或小型养殖场操作。养殖螺蛳首先需要准备一个适宜的环境,通常选择鱼缸或水缸作为养殖容器。在底部铺设20cm厚的池塘淤泥是关键,因为螺蛳喜欢在富含...

    2023年中国螺蛳粉行业发展现状及消费行为分析报告.docx

    2023 年中国螺蛳粉行业发展现状及消费行为分析报告 本报告对2023年中国螺蛳粉行业发展现状进行了深入分析,并对消费行为进行了研究。本报告首先对螺蛳粉行业的发展现状进行了概述,包括螺蛳粉行业的发展驱动因素、...

Global site tag (gtag.js) - Google Analytics