模板类和模板函数都是非常有用的工具。例如sqr()函数可以计算平方数,任何定义了乘法运算的数据类型(数字,矩阵)都适用。标准容器类(如list)都是模板,这样对于每个新类型无需重写了,这正是使用旧版的C++时真正头疼的事情,因此我认为ISO的标准是个伟大的进步。然而,在这个过程中有些东西用得过头了。
例如:标准库中得string 和iostream 都是使用"character traits"类型作为参数。这意味着同一个basic_string<>类可以用于ASCII字符串,也可用于Unicode,甚至用于火星人的三字节字符串(原则虽然如此,但许多版本都只是实现了ASCII字符串,看起来有点滑稽)。标准要求这些常用类必须实现成模板形式,而这些类几乎涉及到所有C++应用。
但是这对性能和调试会带来许多麻烦。下面几个试验解释了这个问题(本试验使用的编译器为VC++6.0)。编译器同时支持新风格的iostream(使用模板)和经典风格的iostream, 因此我们能比较他们二者的版本实现。第一个测试程序当然是使用"Hello, Word"了,新风格的编译时间是经典风格的2倍。另一个更正规的例子大约有200行,每行输出10个变量用于计数。这个测试程序最显著的结论是编译速度:新风格版本花了10秒编译完成,而旧版本只使用了1.5秒。10秒时间可并不少,可以完成很多事情。另外,新风格版本的可执行文件的大小为115K,而旧版本只有70K。你的测试数据可能有些出入,但是整体结论是一样的:当使用新版本时,会有更慢的编译速度和更大的可执行文件。这并不是因为微软公司编译器的问题,使用GCC测试也会得到同样的结论。
当然,和过去不一样,可执行文件的大小并不是那么重要,现在,可编程设备种类正快速增长,包括许多信息应用,如遥控、手机、智能冰箱、基于蓝牙技术的咖啡机等等,在这些应用中内存近几年都会是十分宝贵的资源。使用标准iostream 而产生的额外的二进制文件,来源于内联了整个模板类的代码,要是没有code bload工具,你很难优化那些重要的操作。对我来说,编译时间问题更严重一些,因为这样意味着更长的等待,从而失去了开发中非常重要原则:互动原则。
现在我们来考虑调试的问题。标准库中string 类的模板实现非常聪明,但并不适合于调试。你会面临使用超长名字的编译器和调试器的信息:
std::basic_string<, std::char_traits<>, std::allocator<>>
当然,在许多应用中我们都需要这种std::string提供的灵活性,例如,需要同时处理ASCII 和Unicode字符串,或者需要定制自己的allocator 等等。但这并不是普遍需求(通常程序员要么只处理ASCII,要么只处理Unicode ), 看起来对于程序员承担这种范型机制有些不公平。这种机制确实让标准库的设计者觉得很有意思,但增加了应用开发程序员使用的复杂度。这似乎颠倒了这个原则:良好的标准库设计应该隐藏其实现的复杂度,而让用户直接使用。但std::string 对其实现的复杂度隐藏得并不够,导致在用户使用过程中不断的遇到设计中的问题。我们不能要求标准库的用户都是专家。标准坚持要求这种特定的实现方式,和标准库的设计初衷相违背,其初衷是只提供公共的接口和包含一些特定功能的类库。自然,这种范型模板对于那些真正去要他们的人是一直有效的。
这些细节考虑同样应用于标准容器,例如list<>容器,list 有一些额外的默认模板参数,用于定义了默认的allocator。当然自己定义allocator 十分有用,但绝大多数人不需要自己去实现。这些泛化的版本完全可以作为单独的模板提供。我承认这样做会让标准库的设计在技术上变得没有以前有意思,但这些库在设计之初就应该考虑到最终用户。篡改一下C++的颂歌:用户不应该为他们不需要的东西买单。
当我们不需要模板的时候,我们不得不使用模板。除此之外,在C++中用范型编程还会遇到另一个的问题。大多数人都同意,标准的algorithm 十分有用。如果你有一个整型的vector, 你可以直接使用下面的语句来排序:
sort(v.begin(),v.end());
但有些应用理解起来十分晦涩:
copy_if(v.begin(),v.end(),ostream_iterator<>(cout) bind2nd(greater<>(),7));
vector<>::iterator li;
(li = v.begin(); li != v.end(); ++li)
(*li > 7) cout << *li;
for_each(ls.begin(),ls.end(),
bind2nd(mem_fun(&Shape::draw),canvas));
ShapeList::iterator li;
(li = ls.begin(); li != ls.end(); ++li)
(*li)->draw(canvas);
for_each(ls.begin(),ls.end(),
lambda(Shape *p) { p->draw(canvas); });
C++ 是一种不可思议的编程语言,小到手机,大到跨国际网络,都有其应用。它非常灵活,能够支持多种编程风格,但这种灵活同样也是其问题所在。编程的艺术在于为特定的问题选择合适编程风格,就像老师总提醒写作文是要选择好的风格一样。我并不想诋毁 C++ 标准库,这里面包含了许多人的辛勤劳动,并为大家提供了一个公共平台。我对于这个标准的态度是,它和范型编程联系过于紧密,从而变成了在说明什么风格是好的编程风格(例如,算法中明显倾向于不要使用显式循环), 同时它也让程序员们不得不介入一些实现细节(如basic_string<>),这样做让人们更加觉得C++ 是只是内核工程师们的编程语言。
文章评论(0条评论)
登录后参与讨论