再驳Linus:思科工程师对C++不得不说的事

当我了解后,我彻底不相信萨丕尔—沃尔夫假说。虽然语言相对论带着一套假说卷土重来,但几乎可以肯定的是,人类思想绝不受制于词汇和语法的牢笼。

E.W. Dijkstra是编程语言领域的萨丕尔,他曾说过:

“给以前接触过BASIC学生教良好的编程几乎是不可能的。作为潜在的程序员,他们精神上已经残废,无再生的希望。使用COBOL更是摧残了他们的心灵,因此,教他们应该被视为刑事犯罪”。

今天,我们有Linus Torvalds对C++的炮轰,他认为使用C++的程序员会破坏他或她接触到任何项目。Linus是这样说的:

“我得出的结论是,任何希望在项目中使用C++而不是C的程序员都是很可能让我生气的程序员,因此我不会给他们机会来搞砸我的项目。C++导致真正糟糕的设计选择。”

Torvalds太过武断

在我所在的大型企业中,我们的名片背面有一句口号:不迷信科技。做到这一点可能不容易,但是的,我们愿意尝试。

对我来说,这个训诫意味着两件事:

1、尝试客观地为工作选择最佳的工具

2、不要让你对工具的偏好指挥完成工作的方法

Linus在他的炮轰中已经说得很清楚,任何使用C++编程的人都很心虚,拿这些规则做借口。

但我有不同的想法,我认为C++显然也有可能成为完成工作最合适的工具。得出这样的结论是相当客观的,我认为Linus显然是被他特殊的技术流派迷惑了。

一个简单的例子

作为我上学期C/C++编程班的期末作业,我要求学生用C实现一个简单的令牌计数程序,目标是要重现下面这段C++代码的行为:

map counts;
string s;
while ( cin >> s )
counts[s]++;
for ( auto ii = counts.begin() ; ii != counts.end() ; ii++ )
cout << ii->second << " : " << ii->first << endl;

这个特殊的程序突出了一些C中没有的C++功能,如:

●C数组,向量多功能替换

● 字符串类

● 使用iostreams安全输入

●关联数组作为标准库的一部分

这个程序用C++来写是相当容易的,只需要处理一下输入流中的错误——有时候甚至这一步都没有必要。

用C实现

用C重写是一个稍微复杂点的任务,因为C的库中没有任何类型的关联数组。解决这个问题的办法很多,我选择了下面的办法:

● 将所有令牌读入一个数组

● 完成后,对数组排序

●完成数组排序后,通过遍历每个令牌获得计数

虽然这个算法占用的空间比C++程序更多。假设你没有碰到gsort()之类病态的问题,花的时间可能都差不多。

我坚信用C++比用C写这个程序更容易,理由如下:

●C I/O缺陷。在C中读字符串有相当多的困难,由于C I/O库没有标准的方法读取长度无限制的字符串(可以使用编译器特定的扩展,但那会引起其它问题),你的输入代码必须检查多种错误情况,或者构建你自己的字符串输入函数。

● C数组的内存管理基本上是一项手工任务,我必须为数组分配原始空间,如果超出了它的长度,我还得小心重新分配,做完后,我还得释放空间。

● C字符串的内存管理有同样的问题。

● 字符串数组的排序使用gsort()只是有一点不方便,gsort()不能提供C++库中sort()函数的性能保证。

这个函数的C版本代码行数要多一些,需要手工完成的任务也要多一些,因此犯错的几率也要大一些。

我喜欢C++版本的最后一个原因是,它同样适合其它类型,如插入和提取运算符类型、比较运算符,都可以使用同样的代码,只需要变一个声明。将实现同样的事情的代码转换成一个函数模板,这样就可以在以后直接拿来使用了。

我一些最好的朋友是C程序员

那么我偏爱C++版本就能说明我是一个语言偏执的人吗?

我认为不能如此下结论,首先,我认为这个程序的C版本也有一些不错的优点:

● 你可以使用POSIX系统调用编写这个程序,除了内存分配和排序。

●这个程序的C版本因为使用了底层I/O,速度将会更快,C++ iostreams在任何时候都表现良好,但它们的分层方法在谈到效率时总是处于不利地位。

因此,对于这样一个程序,语言的选择最终归结为背景,如果你相信80/20规则,并且它不是程序的核心部分,你可能认为这个代码应该用C++编写。代码行数越少,犯错的几率就越低,效率可能不是最大的问题。

如果这是一段很关键的经常执行的代码,你可能会认为C是最佳选择。如果选择C,请一定要花点额外的时间进行代码评审,确保没有内存泄露和指针错误,只有那样,你才上路了。

抚平Linus的情绪

那么Linus看到上面的代码是如何炮轰的呢?我斗胆猜一下,任何像样的C++程序员都会写出类似的代码。Linus说:

“你一定使用了这种语言“好的”库功能,如STL和Boost,它们对你的程序可能有帮助,但当它们不工作时,会引起无尽的痛苦”。

在这个程序中,我好好利用了曾是STL一部分的标准库组件,它们作为标准的一部分已经有十多年的历史。它们的表现真的很好,在我所知道的任何编译器中,也没有可移植性或正确性问题。

“你会发现某些抽象编程模型不是很有效,但现在你所有的代码都依赖于所有围绕它的好对象模型。如果不重写你的应用程序,那么你是无法修复它的。”

的确,尽管C++不能为我做这些事,我只有设法自己编写程序,不使用任何抽象——没有新的类,没有接口,基本上完全是靠C代码完成许多有用的类的。

但我认为这是规则而不是例外。

“换句话说,只有这样才能做得好,效率高,系统级和可移植的C++最终会限制自己,但这一切在C中都是可行的。”

除非C有容器类,字符串类,类型安全I/O,以及RAII这样的技术,这句话才会变成真的。现在来看,它完全是胡说。

在现代C++可用之前,我可能会坚持使用一个简单的管道来完成这个任务:

tr [:blank:] '\n' | grep -v "^$" | sort | uniq -c

事实上,我可以在编译语言中做同样的事。只要它们能给我一些灵活性,我想我不用成为一个偏执狂就能明白这个事实。你对此又是怎样看的?

作者简介:Mark Nelson,现任思科公司工程师,《The Data Compression Book》的作者,《Developing Cisco IP Phone Services: A Cisco AVVID Solution》的协作者。

相关推荐