被误解的C++——软件工程

被误解的C++

传统上认为,C++相对于目前一些新潮的语言,如Java、C#,优势在于程序的运行性能。这种观念并不完全。如果一个人深信这一点,那么说明他并没有充分了解和理解C++和那个某某语言。同时,持有这种观念的人,通常也是受到了某种误导(罪魁祸首当然就是那些财大气粗的公司)。对于这些公司而言,他们隐藏了C++同某某语言间的核心差别,而把现在多数程序员不太关心的差别,也就是性能,加以强化。因为随着cpu性能的快速提升,性能问题已不为人们所关心。这叫“李代桃僵”。很多涉世不深的程序员,也就相信了他们。于是,大公司们的阴谋也就得逞了。

这个文章系列里,我将竭尽所能,利用一些现实的案例,来戳破这种谎言,还世道一个清白。但愿我的努力不会白费。

软件工程

一般认为,使用Java或C#的开发成本比C++低。但是,如果你能够充分分析C++和这些语言的差别,会发现这句话的成立是有条件的。这个条件就是:软件规模和复杂度都比较小。如果不超过3万行有效代码(不包括生成器产生的代码),这句话基本上还能成立。否则,随着代码量和复杂度的增加,C++的优势将会越来越明显。

造成这种差别的就是C++的软件工程性。在Java和C#大谈软件工程的时候,C++实际上已经悄悄地将软件工程性提升到一个前所未有的高度。这一点被多数人忽视,并且被大公司竭力掩盖。

语言在软件工程上的好坏,依赖于语言的抽象能力。从面向过程到面向对象,语言的抽象能力有了一个质的飞跃。但在实践中,人们发现面向对象无法解决所有软件工程中的问题。于是,精英们逐步引入、并拓展泛型编程,解决更高层次的软件工程问题。(实际上,面向对象和泛型编程的起源都可以追溯到1967年,但由于泛型编程更抽象,所以应用远远落后于面向对象)。

一个偶然的机会,我突发奇想,试图将货币强类型化,使得货币类型可以采用普通的算术表达式计算,而无需关心汇率换算的问题。具体的内容我已经写成文章,放在blog里:http://blog.csdn.net/longshanks/archive/2007/05/30/1631391.aspx。(CSDN的论坛似乎对大文章有些消化不良)。下面我只是简单地描述一下问题,重点还在探讨语言能力间的差异。

当时我面临的问题是:假设有四种货币:RMB、USD、UKP、JPD。我希望能够这样计算他们:

RMB rmb_(1000);

USD usd_;

UKP ukp_;

JPD jpd_(2000);

usd_=rmb_; //赋值操作,隐含了汇率转换。usd_实际值应该是1000/7.68=130.21

rmb_=rmb_*2.5;//单价乘上数量。

ukp_=usd_*3.7;//单价乘上数量,赋值给英镑。隐含汇率转换。

double n=jpd_/(usd_-ukp_);//利用差价计算数量。三种货币参与,隐含汇率转换。

而传统上,我们通常用一个double或者currency类型表示所有货币。于是,当不同币种参与运算时,必须进行显式的汇率转换:

double rmb_(100), usd_(0), ukp_(0), jpn_(2000);

usd_=rmb_*usd_rmb_rate;

ukp_=(usd_*usd_ukp_rate)*3.7;

double n=jpd_/((usd_*usd_jpd_rate)-(ukp_*ukp_jpd_rate))

很显然,强类型化后,代码简洁的多。并且可以利用重载或特化,直接给出与货币相关的辅助信息,如货币符号等(这点我没有做,但加上也不复杂)。

在C++中,我利用模板、操作符重载,以及操作符函数模板等技术,很快开发出这个货币体系:

template<int CurrType>

class Currency

{

public:

   Currency<CurrType>& operator=(count Currency<ct2>& v) {

   }

public:

   double _val;

};

template<int ty, int tp>

inline bool operator==(currency<ty>& c1, const currency<tp>& c2) {

}


template<int ty, int tp>

inline currency<ty>& operator+=(currency<ty>& c1, const currency<tp>& c2) {

}

template<int ty, int tp>

inline currency<ty> operator+(currency<ty>& c1, const currency<tp>& c2) {

}

总共不超过200行代码。(当然,一个工业强度的货币体系,需要更多的辅助类、函数等等。但基本上不会超过500行代码)。如果我需要一种货币,就先为其指定一个int类型的常量值,然后typedef一下即可:

const int CT_RMB=0; //也可以用enum

typedef Currency<CT_RMB> RMB;

const int CT_USD=1;

typedef Currency<CT_USD> USD;

const int CT_UKP=2;

typedef Currency<CT_USD> USD;

const int CT_JPD=3;

typedef Currency<CT_USD> USD;

每新增一种货币,只需定义一个值,然后typedef即可。而对于核心的Currency<>和操作符重载,无需做丁点改动。

之后,我试图将这个货币体系的代码移植到C#中去。根据试验的结果,我也写了一篇文章(也放在blog里:http://blog.csdn.net/longshanks/archive/2007/05/30/1631476.aspx)。我和一个同事(他是使用C#开发的,对其更熟悉),用了大半个上午,终于完成了这项工作。

令人丧气的事,上来就碰了个钉子:C#不支持=的重载。于是只能用asign<>()泛型函数代替。之后,由于C#的泛型不支持非类型泛型参数,即上面C++代码中的int CurrType模板参数的泛型对等物,以及C#不支持泛型操作符重载,整个货币系统从泛型编程模式退化成了面向对象模式。当然,在我们坚持不懈的努力下,最后终于实现了和C++中一样的代码效果(除了那个赋值操作):

assign(rmb_, ukp_);

assign(usd_, rmb_*3.7);

我知道,有些人会说,既然OOP可以做到,何必用GP呢?GP太复杂了。这里,我已经为这些人准备了一组统计数据:在C#代码中,我实现了3个货币,结果定义了4个类(一个基类,三个货币类);重载30个算术操作符(和C++一样,实现10个操作符,每个类都得把10个操作符重载一遍);6个类型转换操作符(从两种货币类到第三货币类的转换操作符)。

这还不是最糟的。当我增加一个货币,货币数变成4个后,数据变成了:5个类;40个算术操作符重载;12个类型转换操作符重载。

当货币数增加到10个后:11个类;100个算术操作符重载;90个类型转换操作符重载。

反观C++的实现,3个货币时:1个类模板;1个赋值操作符重载模板;10个算术操作符重载模板;外加3个const int定义,3个typedef。

10个货币时:1个类模板;1个赋值操作符重载模板;10个算术操作符重载模板;const int定义和typedef分别增加到10个。

也就是说C++版本的代码随着货币的增加,仅线性增加。而且代码行增加的系数仅是2。请注意,是代码行!不是类、函数,也不是操作符的数量。而C#版本的代码量则会以几何级数增加。几何级数!!!

这些数字的含义,我就不用多说了吧。无论是代码的数量、可维护性、可扩展性C++都远远好于C#版本。更不用说可用性了(那个assign函数用起来有多难看)。

我知道,有些人还会说:货币太特殊了,在实践中这种情况毕竟少见。没错,货币是比较特殊,但是并没有特殊到独此一家的程度。我曾经做了一个读取脚本中的图形信息,并绘图输出的简单案例,以展示OOP的一些基本概念,用于培训。但如果将其细化,可以开发出一个很不错的脚本绘图引擎。其中,我使用了组合递归、多态和动态链接,以及类工厂等技术。就是那个类工厂,由于我使用了模板,使得类工厂部分的代码减少了2/3,而且没有重复代码,更易维护。关于抽象类工厂的GP优化,Alexandrescu在其《Modren C++ design》中,有更多的案例。同样的技术,还可以推广到业务模型的类系统中,优化类工厂的代码。

如果还不满意,那么就去看看boost。boost的很多库实现了几乎不可想象的功能,比如lambda表达式、BGL的命名参数等等。它为我们很多优化软件代码新思路,很多技术和方法可以促进我们大幅优化代码,降低开发成本。

最后,如果你认为C#的最大的优势在于.net平台,那我可以告诉你,这个世界上还有一种东西叫C++/CLI,完全可以满足.net的开发,而且更好,足以擦干净.net那肮脏的屁股。不过,这将会是另外一个故事了…


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342