C++之歌——求泛型给我安慰

longshanks 2007-09-21 05:14:08
编程是艺术,这无可否认。不信的去看看高大爷的书就明白了。艺术对于我们这些成天挤压脑浆的程序员而言,是一味滋补的良药。所以,在这个系列中,每一篇我打算以艺术的形式开头。啊?什么形式?当然是最综合的艺术形式。好吧好吧,就是歌剧。当然,我没办法在一篇技术文章的开头演出一整部歌剧,所以决定用一段咏叹调来作为开始。而且,还会尽量使咏叹调同文章有那么一点关联,不管这关联是不是牵强。

求泛型给我安慰

“求爱神快给我安慰,
别让我再悲伤流泪!
让我丈夫回转身旁,
或者让我死亡!”

这是W. A. Mozart著名歌剧《费加罗的婚礼》第二幕开头,伯爵夫人罗西娜的一段摇唱。太美了,就是前奏长了点。《费加罗的婚礼》取材于法国戏剧家博马舍创作的“费加罗三部曲”的第二部。伯爵阿尔马维瓦在《塞维利亚理发师》(三部曲第一部,由罗西尼创作成歌剧,晚于《费加罗的婚礼》)中,苦苦追求罗西娜,在费加罗的帮助下,终成眷属。但阿尔马维瓦终于显露出他的喜新厌旧、朝三暮四的本性。成为伯爵夫人的罗西娜受到冷落,郁郁寡欢。在自己的卧房中,忧伤地唱起了这首咏叹调…

我们知道,程序员的朝三暮四,虽说不一定都是本性,但也几乎成了一种习惯。每当出现一种新的语言,很多程序员也不顾一切地投怀送抱。不管这种语言是好是坏,进步还是退步。尽管我们无法指责这种行为,就像无法把阿尔马维瓦伯爵告上法庭。但是,事实的真相还是应当昭示与天下的。
牢骚就不再多发了。今天的主题是GP。不,不是电池,也不是摩托比赛。全称是Generic Programming,泛型编程。一种非常强大,却有为人们所忽略的关键性技术。这种技术代表的是未来。
不,不要提Java、C#。他们的那种也叫Generic(泛型)的东西,和真正的GP沾不上什么边。只能算作大号的OOP,一会儿就会知道为什么我这么说。
本文起源于TopLanguage(http://groups.google.com/group/pongba)上的一次讨论(http://groups.google.com/group/pongba/browse_thread/thread/e553a21476ba2ebd)。我在讨论中做了一个案例,以说明GP的作用。现在我把这个案例整理出来,一同探讨。这个讨论还涉及了更深层次的理论和技术,其余内容看那个帖子。
案例提出这样一个需求:
搞过帐务系统或者学过财务的都应该知道,帐务系统里最核心的是科目。在中国,科目是分级的,(外国人好像没有法定的分级体系),一般有4、5级,多的有7、8级,甚至10多级。不管怎么分,科目的结构是树。
在科目上有一个操作称为"汇总",就是把子科目的金额累加起来,作为本科目的金额。这实际上是对指定科目的所有下级科目的遍历汇总。这是一个非常简单,但却非常重要的帐务操作。
首先,我通过两种方法:OOP和传统的SP来实现这种操作。先来看SP:
//科目类,具备树形结构
class Account
{
public:
typedef vector<Account> child_vect;
pubilc:
child_vect children(); //子级科目
int account_type(); //本科目类型
float ammount(); //本科目的凭证金额累计,非最明细科目返回0
};
//汇总算法
double collect(Account& item) {
double result(0);
Account::child_vect& children=item.children();
if(children.size==0) //最明细科目,没有子科目
return ammount(item.ammount);
Account::child_vect::iterator ib(children.begin()), ie(children.end());
for(; ib!=ie; ++ib)
{
result+=collect(*ib, filter, ammount);
}
return result;
}
当我需要对一个科目对象acc_x执行汇总算法,那么就是这样:
double res=collect(acc_x);
这非常简单。不过请注意,我这里还是利用了OOP的封装机制,为了使Account的实现和接口分离。但所使用的算法/数据分离的模式,则是SP风格的。
OOP风格的更加简单:
class Account
{
public:
child_vect children(); //子级科目
int account_type(); //本科目类型
double ammount() { //此成员直接执行子级科目的汇总任务
double result(0);
if(children.size==0) //最明细科目,没有子科目
return m_ammount; //或从其他途径获得,如数据库访问
T::child_vect& children=item.children();
T::child_vect::iterator ib(children.begin()), ie(children.end());
for(; ib!=ie; ++ib)
{
result+=ib->ammount();
}
return result;
}
};
使用起来是这样:
double res=acc_x.ammount();
都很好。不过,现在项目增加了需求,我们面临挑战:
假设现实世界的古怪客户,使我们面临一个挑战:他们的业务模型中,有部分科目不参与汇总计算,是一群特殊的科目。(这种科目我还真见过)。
那么,SP方式,可以另写一个collect函数:
double collect(Account& item) {
//假设g_SpecialAccounts是个Singleton,负责管理特殊科目
if(g_SpecialAccounts.IsSpecial(item->account_type()))
return 0
… //其余代码与原来的collect相同
}
而OOP方式,则需要修改Account类(也可以利用重载和多态):
class Account
{
public:
double ammount() {
//假设g_SpecialAccounts是个Singleton,负责管理特殊科目
if(g_SpecialAccounts.IsSpecial(account_type())
return 0;
}
… //其余代码与原来的Account相同
};
相比之下,SP更灵活些。如果我没有Account的源码,或者我无法修改Account,那么我可以直接重写一个collect(比如,collect_x)也能解决问题。而在这种情况下,OOP方式,只能重载或重写这个类。(重载不仅仅需要重写相关成员,而且还需要编写诸如构造函数等辅助代码)。更深层次的因素,是代码耦合的问题。关于这个问题请看前面给出的那个讨论。
接下来的一个需求,则提出了更大的挑战:
我们如果注意的话,MIS系统中有很多地方同科目有着相同的逻辑结构。比如,销售部门的分销组织机构,一个企业的部门组织机构。在这些结构上,通常也会发生汇总操作,比如某个省的分销商业绩汇总,或者某个部门的人数汇总。
于是,充满优化意识的程序员,会想到复用在帐务系统上已有的成果。假设我们定义了部门类:
class Department
{
public:
typedef vector<Account> child_vect;
public:
child_vect Children();
int dept_type();
int employee_num();
...
};
对于SP方案而言,意味着需要写一个collect算法,可以同时用于这两个(甚至更多)的类型。我们努力地尝试着:
double collect_g(void* item, bool (*pred)(void*), void (*mem)(void*, void *)) {
if(pred(item))
return 0;
double result(0);
vector<void*>& children=item.children();
if(children.size==0) //最明细科目,没有子科目
{
mem(item, &result);
return result;
}
vector<void*>::iterator ib(children.begin()), ie(children.end());
for(; ib!=ie; ++ib)
{
result+=collect(*ib, pred, mem);
}
return result;
}
此外,需要为Account和Department分别编写两个辅助函数:
//Account
bool Account_Pred(void* item) {
Acount* acc_=(Account*)item;
return g_SpecialAccount.IsSpecial(acc_);
}
void Account_Ammount(void* item, void* val) {
Acount* acc_=(Account*)item;
*((double*)val)=acc_->ammount();
}
//Department
bool Dept_Pred(void* item) {
Department* dpt_=(Department*)item;
return g_SpecialDepartment.IsSpecial(dpt_);
}
void Dept_EmpNum(void* item, void* val) {
Department* dpt_=(Department*)item;
*((int*)val)=dpt_->Employee_Num();
}
用起来,则是这样:
double res1=collect(&acc_x, &Account_Pred, &Account_Ammount);
int res2=collect(&acc_x, &Dept_Pred, &Dept_EmpNum);
...全文
2256 90 打赏 收藏 转发到动态 举报
写回复
用AI写文章
90 条回复
切换为时间正序
请发表友善的回复…
发表回复
quentinliu 2007-10-14
  • 打赏
  • 举报
回复
csdn上居然有这么牛地人?
jwqu 2007-10-13
  • 打赏
  • 举报
回复
强烈mark
longshanks 2007-10-08
  • 打赏
  • 举报
回复
to sjjf:
看来是我理解错了。不过请给点代码吧,我有点迷糊了。(昨晚12点才下飞机,现在还头晕呢)。:P
Vitin 2007-10-03
  • 打赏
  • 举报
回复
这个需求涉及两个方面:如何做过滤,如何做统计。

先说说统计。
jeffchen 提出的用Visitor模式是一个通用的方法。事实上,不管统计和过滤,都可以用同一个Visitor解决。(因为Visitor的本质就是遍历+逐个操作)当然,为了解藕过滤和统计这两种不同的操作,这里仅建议用Visitor模式做统计。
LZ的方法是让将遍历交给泛型算法,由成员函数逐个操作。这当然也没有问题,两者的区别只是应对变化的手段不同:
1、当遍历方法变化时,jeffchen修改Accept函数,LZ修改泛型算法;
2、当操作方法变化时,jeffchen修改Visitor,LZ修改实体类的成员函数。

再说说过滤。
水晶剑锋的方法可以解决它。以他的方法为基础,做一些细化。
我觉得可以定义一个通用的tree容器(为什么STL中没有提供呢?因为通用的tree不够普遍?也许除了Sequence和Associative外,还要增加第三种类型的容器,Tree;以及第四种,Graph)。tree容器的遍历要仔细定义,用于现有容器的iterator概念似乎不太适合(至少不全面,效率也不高)。可以考虑定义专门的tree_iterator,与random_iterator等不同,它还提供了类似get_child,get_parent这样的操作(用什么操作符代表它们呢?可能就是因为它很难找,所以STL中不定义tree?我觉得get_child可以重载operator[],get_parent可以重载单目的opeator-),此外,还提供一个传统的iterator,可以用做先根遍历等,它是一个bidirectional_iterator。这样可以与现有的算法如find,for_each等无缝连接。另一方面,一些通用算法可以考虑为tree_iterator专做一份实现,以提高效率。
实体类可以这样定义:
typedef tree<AccountItem> Account;
typedef tree<DepartmentItem> Department;
然后用View以及Filter做过滤。
View::get_result返回一个已经过滤的tree,这样统计(或者其他操作,如继续的过滤,相当于多个条件的“与”)仍然是在同一个类型的tree上进行。与过滤无关。
至于“或”条件过滤,一种方式是考虑View本身直接支持“或”,可设置多个Filter;还有一种是为tree提供合并操作(水晶剑锋提出View的合并,也是一种方法),当然合并的规则不太容易定义,要仔细考虑。

用以上的方法,统计和过滤已经完全无关了。

与编码相比,分析与设计其实更重要。以设计模式为代表,OO在设计方面已经很成熟。而泛型设计才刚刚开始。目前的编程在设计方面仍然是以OO为主,泛型等仅用于编码上(包括现在这个案例)。我觉得这远远没有体现泛型的优点。OO除了OOP外,还有OOD、OOA。什么时候GD、GA能与GP相提并论了,泛型才真正地走向成熟。
当然,我们不能为GD而GD。OO在设计方面体现了它强大的抽象能力和组织能力,它抽象出类,并将类与类、对象与对象的关系阐述清晰,形成完整的关系网络和协作网络,无论在时间上、空间上都组成了一个有机的整体;所有这些,不仅是“能够”,还要是“最优”。在泛型中,首先应该回答:如何抽象出Concept,如何将Concept与Concept、Modeling与Modeling的关系阐述清晰,如何形成完整的关系网络和协作网络(或泛型所特有其他的网络?),如何在时间上、空间上都组成了一个有机的整体;以及如何做到最优。而且,只有GD在这些方面(以及OOD不具备的方面)体现出优势,才有使用GD的必要。否则,OOD*(GP+OOP+...) 已经足够了。所以,泛型还需要实践上和理论上的不断积累和突破,还有很长的路要走。
sjjf 2007-10-03
  • 打赏
  • 举报
回复
我要表达的意思和lz理解的意思可能还存在差异。
这不是理论,在05年的时候,它是被实现了的(java实现)。
我喜欢泛泛而谈,也就是传说中的忽悠,不喜欢写代码,
我认为, 能不写代码就不写代码,那玩意儿,写多了也没有什么好处。
(只是有些时候,程序员确实喜欢靠代码说话,但个人认为,应该靠逻辑说话,代码只是一种检验)

回答lz的问题吧,设计过的细节我已经记的不太多了,凭着大体的思路忽悠吧。
1,setTarget的参数是目标元素的集合,在本例中是 account。gp在这时候可以发挥点作用,
但是也不是很大。
gp或者用接口的方式实现,问题都不是很大,cpp的接口用抽象类来实现,
从某种意义上来说,模版和继承都是一种泛化关系,
说白了,如果不能抽象出不变的东西,用gp和继承都不能解决问题。
(我一直在思考继承,接口与实现的实质与型和值的关系(现在又加入了模板),
只是还没有入门,我想如果等到我能理解了lambda算子对0的定义后,我应该能参透。)

department不是继承自account的。
关联关系你可以google一下。关联类是指用来代替关联关系的另一个独立的类。
如果做过数据库的orm设计,就应该比较熟悉,常常,一个连接两个表的关系表被映射成关联类。
按照你给的例子,我的理解是department 和 account的关系是应该是关联关系,
就是一个department里面会有某个分支的account。
关联关系,不外乎是聚合,合成。
在cpp/java中,关联的实现,一般是成员类实体,或者指针/引用,或者是抽离出一个类实体来做关联对象。
如果判别用聚合和合成,google一下应该都能明了。但是知道何时用聚合,何时使用合成,
却跟问题域相关,我还记得一本oo设计的书举了一个case,同是桌子这个组合体,在消费者眼里,
桌面和4条腿是合成关系,但是在木工眼里确是聚合关系。这样的例子,在google上一大把,
正确地理解这些基本关系才能更好的进行oo设计。oo核心在于分析设计,而不是怎么实现,
设计到实现这一段已经很成熟了,对于语言的设计者,实现这个东西是很难的,
但对于我们这些语言使用者,学学就算了,要掌握核心。
oo分析设计需要把握的元素是uml的那些东西。理解uml那些元素的意义,才能理解了oo,
这时候加点力气到具体的语言,例如cpp,java之类的,就能够大体把握基本的oo设计,
结合一门具体的oo开发方法论,我想也应该能够熟悉的进行oo设计了。
有点类似武功,先知道理论上怎么样才能有效地利用人体各个部位进行攻防,(oo描述元素的意义)
然后再把一些基本分解动作练习熟悉,如直拳,正踢腿,肘击等,每一个门派都有不同的侧重点如同各门语言一样。(具体的语言的实现)
熟悉了基本功,再把那些动作连贯起来,形成套路,这时候各门各派的整体特征才出来了。(开发方法论)
如果是大师,还能创造自己的套路....

在具体语言层面去争论那些描述点(语言特征),没有什么意义,那些描述点在它们的套路里面组合起来才能发挥它的作用,我也敢保证,没有描述点在任何问题域都是赢家。

department类一会儿再议。

2.关于view类,它一开始的设计目的之一是可以应付各种足够复杂的结构并实施有效的过滤。
把判断条件委托给filter类,用iterator模式应对结构的异构,可以做到这点。

当然它也不是没有缺点,当需要多个元素联合起来判断一个过滤条件时
注意,不是一个元素的几个属性,而是几个元素联合起来判断,
这时候简单的filter+简单的iterator就应对不了了,
当然也不是没有办法,在filter里面维护未决的状态列表,
(有可能遍历到一个元素时候会有好几个未决状态被解决。从而返回好几个元素)
其中可能会涉及到访问已访问过的元素,这时iterator需要增加一个方法来应对这种情况。
(如果有这样的需求,总会存在着这样的方法从当前的元素到达目标元素的,
如果没有,那么逻辑上也不会成立)
科目可能不会有这种问题,但是别的环境下就会有这个问题。

还有一个缺点是,效率很低,有多少个view就需要最少遍历多少遍。
当然如果迫于效率,有些时候也可以把一些view合起来写,一次遍历全部搞定。
但是不是所有的情况都可以把view合并在一起的。
什么时候可以合并view,什么时候不能合并view
我忘了,当时针对的情况还没有总结抽象出来。

分开写,也有好处,可以重用以前的代码,比如,以前只有一个按照特殊科目分类的view
现在多加了一个部门的view进来,两者是 or 或者 and的关系,那汇总类只需要做简单的活
就ok了,没有白吃的午餐,便利需要支付代价--效率。
实际上,在项目中(java项目),相当于汇总类那一部分,我采用template模式,其中的
view之间的关系我采用其他技术(反射技术+antlr字符串解析)剥离到配置文件中,

这种做法被实现在于网络的设备的监控上,md,一个设备以及状态被按做n多个结构进行统计,按照组织结构,按照拓扑结构,按照设备种类,按照xxx......并且需求分析还显示出某些统计方式由这些统计方式混合而成。
这种情况迫使我去思考有没有一种好的方式来很好的解决问题,
于是我开始思考:这种结构和那种结构有什么异同,是否可以统一在一起...

View的setTarget()只是指定分类的元素而已。具体可以选一种容器,可以根据自己的需要选一种。
setFilter(filters ) filter才可能和结构节点和分类元素的属性有关系。如果以department类
建立结构,并且在判断的时候需要用到结构的节点,那么filter类可以和department类相关,
我不知道具体的环境信息,所以,没有办法设计filter类填充外界信息的那段,但是至少
fitler类至少有一个isPass()方法的接口是有的。

getresult做四件事情:建立结构,挑选iterator遍历结构,
使用filters,根据需要填充信息入filter然后进行判断,对结果集的维护。
lz很关心类型转换,而类型我觉得在设计期间不是很重要,倒是可以作为运行期检测错误的一种
手段。

本例中是用一个view类还是定一个接口,由n个view类来实现,问题不大,在本例中争执点其实在iterator。

我没有比较过这里面用gp有什么优势,因为java 1.4还没有模板,
但是我觉得用了模板,恐怕不能做到像插件一样的装卸类。因为gp是静态编译时完成。
比如把view之间的关系表达式是写到配置文件,
用时再解析表达式并借助反射机制构造类实体,再计算表达式,
有view要改的时候直接改完编译扔进去就ok了,有新类增加也如此炮制,
不需要停服务系统。
Mephisto_76 2007-10-02
  • 打赏
  • 举报
回复
to jeffchen:
能不能把你考虑的visitor pattern的visitor的代码贴一下呢?

因为我感觉visitor pattern 的一个最主要的缺点,visitor需要依赖于每一个具体的Node。如果Node类型是稳定的,那很好。否则,整个visitor都必须要修改。
longshanks 2007-09-27
  • 打赏
  • 举报
回复
纯OO的C++代码实在跟java/C#之类无法比
==============================
这得看比什么。
如果比易学易用性,那么java/c#好。
如果比功能和灵活性,java/C#无法与C++相比。

gp还是非常新的技术,即便是Meyes这样的高人,也承认在2001年以后,才开始真正地了解gp。何况我们这些后来者呢?
我们一起努力吧。
Zhang_637 2007-09-27
  • 打赏
  • 举报
回复
佩服
smalllixin 2007-09-27
  • 打赏
  • 举报
回复
GP和OOP的结合才是C++的出路,纯OO的C++代码实在跟java/C#之类无法比,纯GP限于广大C++ developer的普遍水平,导致代码不易理解(从回帖的人种就能看出来…… 很多人对gp了解很有限,呵呵,冷静冷静)
C++的功能强大会令我们在任何情况都有很好的解决方案!fighting! Cpper
jeffchen 2007-09-27
  • 打赏
  • 举报
回复
本人倾向于使用Visitor模式解决collect问题。通过在Account和Department中定义Accept函数,封装对其自身结构的遍历算法。而具体的操作和使用的条件,则由具体的Visitor负责。
同时,使用GP解耦Accept函数与Visitor。如:
class Account
{

template<class Visitor>
Accept(Visitor& vi)
{
vi.enter(*this);
// for each all the children
for(child_vect::iterator it=children().begin();it!=children().end();it++)
it->Accept(vi);

vi.exit(*this);
}

};

同样,具体的Visitor也可以使用GP构造,其类似longshanks(longshanks)上帖中提到的View
longshanks 2007-09-26
  • 打赏
  • 举报
回复
一日之际在于晨。清早我差不多想明白了sjjf(水晶剑锋)方案,这里我复述一下,看看对不对。
这个方案的主要思路是将算法拆分成几个不同的部分:
首先,把树形结构展开成list或array之类的线性结构。展开的同时,或展开之后对其进行过滤。随后直接对这些线性结构执行运算。
同时,用view1和view2分别维护科目和部门的结构。View类应该是两者的基类或接口。
很漂亮的设计,我很喜欢。但是在本帖案例的题设下,存在一些实现上的问题:
1、类型归一化。View的setTarget()函数只能接受一个类型,在这个案例中sjjf(水晶剑锋)给出了account作为参数类型。而account和department是两个类型,必须归一化。所有department成了account的关联类。我不是很明确关联类的含义,不过应该是将这两种类型归一化的一种手段。我的理解就是,department继承自account,使得account成为department的一个适配器。
2、view1和view2维护科目和部门结构。实际蕴藏着展开科目结构算法,也就是把偏序关系转换成等价关系。同时在科目上应用filter,完成过滤。考虑到科目和部门结构都是树形结构,那么这种展开算法有很大的相似性。(甚至可以说是几乎一样的)。这种代码在两个类重复存在。不过,这不是问题,因为deparment和account的类型已经归一化,无需两个view,一个即可。
3、getResult得到结果类型。是department还是account?这也不是问题,因为department和account归一化,可以直接返回归一化后的类型——account。
4、累加计算。此时的累加计算只需针对线性的list或array执行,一个循环便可解决问题(当然我们可以简单地运用一个accumulate和mem_fun简化,不过这就使用了gp)。但是,请注意,此时我们手中的是account类型的list或array。对于account本身,直接调用ammount即可。但是对于department上的employee_num而言,比较复杂。必须利用rtti获取动态类型,然后利用类型信息做分派。无论从性能,还是从代码冗余,都大大恶化了。
5、类型耦合,department和account关联,我这里假设是继承。那么这两个类型严重偶合在一起。
以上所有问题都可以追溯到一个源头,即最初的类型归一化。如果想要解决这个问题,我们需要一个可以同时接受不同类型的setTarget()和setFilter(),以及一个可以吐出不同类型list或array的getResult()。要实现这个需求,也只有gp这一条路:
template<typename T, typename P>
class View
{
public:
typedef vector<T> res_vect_t;
public:
void setTarget(T& item);
res_vect_t getResult();
private:
P p;
};
这样,我们只需针对不同的类型实例化这个模板,随后可以很方便地使用:
typedef View<Account, AllTrue<Account> > AccountView;
AccountView v1;
v1.setTarget(acc_x);
AccountView::res_vect_t acc_list=v1.getResult();
double res=accumulage(acc_list.begin(), acc_list.end(), mem_fun(&Account::ammount));

typedef View<Department, AllTrue<Department> > DepartmentView;
DepartmentView v2;
v2.setTarget(dept_x);
DepartmentView::res_vect_t dept_list=v2.getResult();
int res=accumulage(dept_list.begin(), dept_list.end(), mem_fun(&Department::employee_num));
更进一步,可以直接用一个泛型算法代替这个类模板:
template<typename T, typename P>
typename T::res_vect_t do_view(T& item, P filter) {
...
}
用起来则更简单:
Account::res_vect_t acc_list=do_view(acc_, AllTrue<Account>());
double res=accumulage(acc_list.begin(), acc_list.end(), mem_fun(&Account::ammount));
如果还想集成些的话:
template<typename T, typename R, typename P, typename Mem>
typename Mem::result_type collect(T& item, P filter, Mem m) {
typename T::res_vect_t acc_list=do_view(acc_, filter);
return accumulage(acc_list.begin(), acc_list.end(), m);
}
这样,就又回到了sp风格的gp实现上:
int res=collect(dept_x, AllTrue<Department>(), mem_fun(&Account::ammount));
只是内部算法有些差异而已。
当然,也可以将View嵌入Account或Department等业务类的实现中,变成oop风格的gp实现;或者将View演化成“算法适配器”方案的gp实现。
这就表明了gp在这类应用问题前的价值和优势。无论采用何种风格开始设计,当遇到类型匹配、代码冗余问题时,都可以利用gp加以解决,有时还能获得更大的灵活性。
兵法曰:兵无常势,水无常形。编程亦是如此。
OnlyHappy 2007-09-26
  • 打赏
  • 举报
回复
没仔细看。感觉和typelist差不多吧
RoseinTrap 2007-09-26
  • 打赏
  • 举报
回复
好强

完全看不懂
lijielijielijie 2007-09-26
  • 打赏
  • 举报
回复
呵呵,是看了高人的讲解哦。看pongba的blog
Mephisto_76 2007-09-26
  • 打赏
  • 举报
回复
牺牲了中午的休息时间,又把提案n2042——concept看了一下,看的有点儿不细致。不过感觉清晰了很多,对concept和concept_map以及约束等等都有了进一步的了解。

最好的是一个concept就是一个用于泛型的概念,但是用于GP时,它又可以作为一个真的(强)类型来用,可以参与重载。concept_map提供了相当于template的特化和部分特化的功能,同时把具体类和concept关联起来,而且这一切都还是语言内置的。

真是振奋啊。

果然是开卷有益。
longshanks 2007-09-25
  • 打赏
  • 举报
回复
再一个案例:
template<has_swap_mem T> //如果有swap成员,用swap成员交换
void swap(T& a, T& b)
{
a.swap(b);
}

template<has_exch_mem T>
requires !has_swap_mem<T>
void swap(T& a, T& b) //如果有exch成员,没有swap成员,用exch交换
{
a.exch(b);
}

template<typename T>
void swap(T& a, T& b) //其他的用临时变量交换
{
T t(a);
a=b;
b=t;
}
使用时,编译器根据T的特征寻找匹配的swap版本。

也可以使用concept_map,把拥有exch的类型"打扮"成has_swap_mem:
template<has_exch_mem T>
concept_map has_swap_mem<T> {
void swap(T& a, T& b) {
a.exch(b);
}
}
这些特性就消除了static tag dispatch这种繁琐的tricks
longshanks 2007-09-25
  • 打赏
  • 举报
回复
另一个例子:
我首先利用concept和concept-map定义Integers和Floats分别代表整数和浮点数两个concept:
concept Integers<typename T>{...}
concept Floats<typename T>{...}
concept_map Integers<int>{};
concept_map Integers<long>{};
...
concept_map Floats<double>{};
concept_map Floats<float>{};
...
然后,我就可以使用了:
template<Integers T> T fun(T a, T b) {...}
template<Floats T> T fun(T a, T b) {...}
这些traits concept一次定义永远使用。(C++0x的标准库应该有这些东西,我没注意过)。
saul2006 2007-09-25
  • 打赏
  • 举报
回复
郁闷,我什么时候才能看懂这样的东东
longshanks 2007-09-25
  • 打赏
  • 举报
回复
Mephisto_76((望美人如梦))说的真好,牛啊!
我再来加点料:
concept是一个非常基础、非常本质的特性,我喜欢说“几于道矣”。:-)
这样的特性通常是多功能的。concept和concept_map一口气提供了众多功能:模板参数约束、static tag dispatch、traits、更简单的显式的局部特化形式。更重要的是,它允许所有这些功能都以一种完全统一的形式。也就是说,可以以直接,不迂回的方式表现这些功能。很多在gp和tmp中的晦涩的技巧都可以不用了。举个例子:

传统的模板特化:
template<typename T> class X; //primary模板,必须有,有时就做成没有body的纯声明
template<typename T> class X<T*> {...}; //对指针特化
template<typename T> class X<T&> {...}; //对引用特化

现在,有了concept和concept_map则可以这样:
concept Pointers<typename T> {...}; //对应指针的concept,描述指针的各种行为
template<typename T> concept_map Pointers<T*> {}; //将指针类型map到Pointers,
// concept_map也可以特化。
concept Refences<typename T> {...}; //对应引用的concept,描述引用的行为
template<typename T> concept_map Refences<T&> {}; //将引用类型map到Reference上

这样所有的指针类型和引用类型就同Pointers和Refences绑定了。使用的时候就很方便了,而且含义也很直观(顺着类型参数读就是了,从左往右):
template<Pointer P> class X{...}; //当类型为指针,用这个模板
template<Refences R> class X{...}; //当类型为引用,用这个模板
既简洁,又直观,简直是美丽。而且这两个concept可以永远用下去,无需再定义。:-D
同样也可以这么处理函数、成员等等。基本上就是把那些traits改成concept_map就行了。
那些恼人的tricks也该退休了。
Vitin 2007-09-25
  • 打赏
  • 举报
回复
LZ推荐的书一定要学习.Modern C++ Design 真的很好,又是一本我早闻大名却未曾一睹的牛书(汗!),现在开始学.
LZ的经历也令我体会到:实践出真知.有追求,就会有收获.所以脚踏实地的人是最聪明的.

引用一句 GP and the STL 里的话(在译序中),大家共勉之:
"永远记住,面对新技术,程序员最大的困难在于心中的怯弱.To be or not to be, that is the question! 不要和哈姆雷特一样犹豫不决,当你面对一个有用的技术,应该果断."

加载更多回复(70)

64,662

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧