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

被误解的C++——磨刀不误砍柴工解决办法

2012-02-10 
被误解的C++——磨刀不误砍柴工磨刀不误砍柴工“磨刀不误砍柴工”这句老话用在C++身上是再合适不过了。如果把C+

被误解的C++——磨刀不误砍柴工
磨刀不误砍柴工
“磨刀不误砍柴工”这句老话用在C++身上是再合适不过了。如果把C++比喻成一把刀,那么它会是一把材质和形状都非常好的刀——只是没有开锋。所以我们要“磨刀”。
C++这把刀材质坚硬,强度也高,或许还进行过表面处理。那自然很难磨,费时费力。不过,一旦磨好,便锋利无比,持久耐用。这还是值得的。
C++的“磨刀”实际上就是开发库,各种可能的库,从基础库开始,到各类应用库。库越多,刀磨得越快。当然了,开发库是有代价的。需要花时间,花精力,以及无限的耐心。
此时,我们便需要做一些估计和四则运算,以便选择如何磨这把刀。
最关键的因素,是某件工作被重复的次数,或者近似的工作的数量。某件工作被重复的次数的含义很明显,如果一再重复自己已经做过的事,明显是愚蠢的行为。“copy-paste神功”利用源代码的可复制性,很容易地避免了重复编码。但是,这也只是稍稍“不那么愚蠢”而已。
当这些被重复的代码发生变化,那么,每一处paste的地方都需要被修改或替换。于是,聪明的人们发明了子程序、函数、类、继承、多态、模板等等五花八门的技术手段。目的便是消除这种“愚蠢”或“不那么愚蠢”的做法。一旦某件工作被做成子程序、函数、类、模板等,实际上便形成了一个库,只是库的应用范围有所差异而已。
相比之下,近似的工作的含义则复杂、含混得多。我们编码时,时常会发现某些工作具有不同程度的相似性。比如,我们写一个排序算法,用于int类型;下次写同样的排序算法,用于double类型;…。有多少需要排序的类型,就要写多少次算法。这些算法并非完全相同(在类型上有所差异),但其结构完全一样。由此,我们可以用一个泛型算法实现所有类型的排序(暂不考虑性能问题和类型concept需求)。
当然,并非所有的代码都具有如此高的相似性。代码的相似性越少,创建抽象的库代码的难度越大。所以,库是有限度的。综合考虑创建库的代价和效用,便可以指导我们是否建立库,或者如何建立库。
对于完全一模一样的代码重复,自不必说,只管做成库代码。因为做这些库代码的工作量,只比编写一次代码的工作量多那么一点。修改也是如此。
而对于相似的代码,情况则复杂得多。一般而言,如果这些相似代码仅有少量的出现,比如3、4处,通常没有必要创建相应的库代码。特别是这些相似代码的相似程度较小,或者代码复杂的时候。此时,创建库的代价很大,但获得的收益也仅有这么3、4处而已。
但必须指出的是,我们在考虑是否创建库时,还必须认真地考虑其他项目,或者未来项目中应用的可能性。如果是某个非常常用的功能,尽管在当前项目中只出现一次,考虑到未来其他项目的应用,也应当将其开发成库。
回到砍柴的比喻。一把没有开封的刀,在一定程度上也能砍下一些树枝,只是砍起来费劲些,也无法砍下较粗的树枝。如果我只需要砍那么几根枝丫,不需要很多,而且以后也不会再去砍柴。那么,一把钝刀也够用了。在这种情况下,如果还费劲地磨刀,着实是一种浪费。相反,如果我今天要砍一整担柴火,或者需要日复一日地砍柴。那么,我最好还是把刀磨磨好再说。
磨刀也有难有易。材质坚硬(俗称“钢火”好)的刀,磨起来费力。但更锋利,更耐用。材质较软的刀,尽管磨起来快。但要使其锋利和耐用,比较困难。(因为材质软的刀,在磨到一定程度后,刃口会向上卷起。如果再反过来磨,又会向反方向卷)。
C++就属于那种材质坚硬的刀。(而且生产厂家出于成本考虑,也没有为其开锋)。于是,作为“职业砍柴人”,有必要好好地磨砺一下这把好刀。(当然啦,也有很多“职业砍柴人”转而使用那些容易磨,或者出厂时已经开锋的“软质刀”)。
磨刀也是有讲究的。(呵呵,我自认为在磨刀方面还是有那么一两手的)。越是硬的刀,越是不能急,一般需要循序渐进。为了不耽误柴火的产量,只能磨一点,用一点。一开始先用大角度,在锋口上磨出快口。尽管大角度的锋口不如小角度的来的锋利,但要比没开锋来得好。更重要的是,大角度锋口所花的时间要比小角度的少很多。由于我手中的是一把好刀,在砍柴的过程中,基本上不会有什么损耗。等到第二天,我再以小角度磨刀。同样,也不打算在第二天就全部搞定,继续用磨了一半的刀砍柴。经过第二天的磨砺,刀会比第一天好用些。然后第三天同第二天一样。以此类推,直到若干天后,刀完全磨好。此后,只需定期打磨一下,维持刀具的锋利即可。相比之下,那些软质的刀则需要更频繁地磨,以维持锋利程度。
好了,刀就磨到这里吧。我们来看看如何“磨”C++。这里就用一个现实的案例来加以说明吧。
现在很多应用软件,特别是MIS类软件,都需要访问数据库,然后把数据提取出来,进行进一步加工,或者直接显示在界面上。下面这样的代码,在软件中想必是随处可见的:
void   OnQueryClicked()
{
DataConnection   dc_(…);
Rowset   rs_(dc_,   “select   …   from   …”);
int   j(0);

m_resGrid.Clear();
m_resGrid.SetColNumber(rs_.ColNumber());

while(rs_.MoveNect())
{
for(int   i=0;   i <rs_.ColNumber();   ++i)
{
m_resGrid.AddRow();
m_resGrid.Cells(j,   i)=rs_.field(i);
}
++j;
}
}
很常规的代码。这里m_resGrid是一个界面上的Grid控件。同时也假定rs_已经采用字符串绑定,可以直接输出字符串。
现在,让我们发挥一下想象力,如果结果集rs_和Grid控件m_resGrid都支持标准STL容器的接口,事情会怎样?很简单,我们可以采用以下的方式重写上述代码:
void   OnQueryClicked()
{
DataConnection   dc_(…);
Rowset   rs_(dc_,   “select   …   from   …”);
int   j(0);

m_resGrid.Clear();
m_resGrid.SetColNumber(rs_.ColNumber());

std::transform(rs_.begin(),   rs_.end(),   std::back_inserter(m_resGrid),   CopyRStoGrid());
}
明显简单多了,不是吗?不过有问题。这个transform仅仅替代了原来的外层循环,即那个while()。而内存循环,也就是rs_的Column上的循环还没有做。
解决的方法也不复杂,也就是CopyRStoGrid的作用:一个执行内循环的函数或函数对象即可:
struct   CopyRStoGrid
{
Grid::Row   operator()(Rowset::Row   const&   row)   {
Grid::Row   gridRow_(row.FieldNum());
std::copy(rs_.begin(),   rs_.end(),   gridRow_.begin());
return   gridRow_;
}
};
这个函数对象编写完成后,便可以一直使用,无需再写。所以,我们可以用一行代码代替原来的两个循环/五行代码(不算花括号)。这个好处看起来或许不算太大,但是考虑到软件中大量存在的结果集向显示控件的数据拷贝,这种差异积累起来就很可观了。
另外一个问题是性能。std::transform()在性能上不会有太大的问题。问题在于CopyRStoGrid上。根据std::transform()的要求,operator()的返回值得是Grid::Row,而且是值返回。非平凡类型的值返回在性能方面是臭名昭著的。现代编译器通常会执行NRV优化,而且像CopyRStoGrid这样的简单函数对象,这种优化是可以得到保证的。即便是无法得到NRV优化,下面会提供一种更直接的优化方案。未来,可以利用C++09的右值引用彻底消除这类性能隐患。
结果集和Grid等控件STL化后,带来的好处不仅仅在于数据复制方面,在其他方面,比如数据过滤、删除、转换、查询、排序等等,都会带来大量的简化。在长时间地开发后,我们已经对循环,手工数据拷贝或转换、查询等等已经习以为常了。很多时候,我们一次又一次地编写一些隐性的算法。我经常看到有人用赤裸裸的循环,在一个Grid上查找某个特定的数据。(这些人也包括我自己)。我也不止一次地在文档中寻找某个控件、容器、或类上,是否有叫Find、Search之类名称的成员函数。相比之下,能够直接用find或find_if这样拥有唯一语义的算法,来查询数据,而不用考虑数据所在的类型,是一件多么惬意的事啊。



[解决办法]
C++最大的问题,恐怕就是这种情况了——用90%的力气去磨一把刀,然后用剩下的10%去砍柴。
[解决办法]
顺便……为何很多人会认为C++在软件开发中不好用,效率不高呢,很可能最主要的一个原因,就是C++语言是一个很难把握好“磨刀”和“砍柴”比例的语言。而且通常“磨刀”的作用被过分高估而且过度重视了。
[解决办法]
唔,可能我表达有问题或者我理解不行,不太明白LZ回复的意思。

磨刀不误砍柴工这个,我是这么看的,取决于你怎么磨这把刀,花多大的功夫,磨刀的时间和你这把快刀砍树节省的时间,肯定有一个最佳的结合点,你究竟要把你的刀磨到多快才合适,花太多时间磨刀是不是反而会降低你工作的效率呢?

C++恰恰在这点上不好把握。C++程序员倾向于设计这么一把刀,它不仅轻,而且比大多数市面上的刀砍起你要砍的树都快,同时还能很容易地处理其他几种类似的树,同时这把刀还能保护你的安全~同时(与C语言不同),这把刀结构合理,任何一处看上去华丽的装饰都有实际的用处,堪称美学的典范。

关键是当你设计这么一把刀的时候,人家拿把钝刀也早把山上的树都砍光了,当然你可以说等我这把刀搞出来,全世界的树我都照砍不误。话是这么讲,万一你其实就准备砍一座山的树呢?我相信就算你准备砍一座山的树,大多数C++程序员也还是会忍不住去设计这么一把刀的。
[解决办法]
用90%的力气去磨一把刀,然后用剩下的10%去砍柴。
**************

美国人就这样干的,花95%的时间去制造可以自动化的工具,花5%的时间完成任务。
而中国人是花5%的时间准备一下,花95%的时间去苦苦干活。

[解决办法]
学习。

成功的要素有两点:使用好的方法;发挥你的才能。
使用好的方法——磨刀不误砍柴工
发挥你的才能——良工不示人以朴

如果磨刀有一定难度,那就对了。做有难度的事,才可以充分发挥你的才能。尽全力取得的成果,才是社会所需要的成果。对刀如此,对人亦然。
相反,如果有人不去磨刀,耽于享受砍柴的轻松,那么他就既没有使用好的方法,又没有发挥他的才能。这样怎么可能成功呢?也许他可以取得一些成绩(以其他某些东西为代价),但是与他原本可以达到的境界相比,差别不可以道里计。

十年磨一剑,百年育新人。人生就是在不断的磨练中度过的,要珍惜自己辛苦磨练出的才能。

当然,具体到C++,写库是一种磨刀。除此之外,还有很多的刀需要磨,比如算法,比如业务知识,比如程序员的直觉……并非说一个人需要磨所有的刀,也并非说好的方法只有磨刀一种。团队合作可以弥补大部分的不足,一个人只要在团队中发挥自己的特长就可以了。

不管怎么说,写C++库都是一种令人振奋的工作。当你亲手写出一个库时,你就会深刻体会到:“良工不示人以朴,磨刀不误砍柴工。”

[解决办法]
楼主在另一个帖子中讲的汇率的那个例子
在java等语言中应该也可以实现
它们不能像c++的模板在编译时推出参数类型
但可以用运行时类型信息啊. 例如在java中可以用xxx.getClass(). 然后把Class对象映射到一个整数.
最后一样可以用这个整数在 汇率数组中查找啊.

热点排行