(转)被误解的C++——软件工程 ---- 大家评价一下这些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个类型转换操作符(从两种货币类到第三货币类的转换操作符)。