|
泛型 :基于策略的basic_string实现(1)
泛型<编程>:基于策略的basic_string实现 Andrei Alexandrescu 这个月的返型<编程>部分有两样新的东西。一样是本篇主题——我们会讨论实现标准库部件basic_string(更多作为string为人所知,为了方便,string是一个basic_string<char>的typedef),一个C++库的重要部件。但真正有趣的是本篇供下载的代码为了在Visual C++ 6.0下工作做了特别处理,VC6编译器有两个为人所知的矛盾特性——它被广泛使用和它对泛型编程支持极弱。 文章附带的代码不是实现一个,或者两个,而是十二个basic_string的不同变化,每个都有各自的优缺点。它们不是玩具程序。我们这里谈论的是完整的,与标准兼容的,工业级强度的东西(呃,当然还是有bug的)。你认为会有大量代码?再想想吧。相信我,本文将会非常有趣。 一种选择不能满足所有人 首先,为什么要有人去很麻烦地实现basic_string呢?你的标准模板库已经实现它了,那么拿出“另外一个”basic_string实现看上去只有教育价值了。 然而,许多在多线程程序里面用过字符串的人知道一个很难解决的问题。标准试图允许“写时拷贝(copy-on-write)”的basic_string实现(写时拷贝被它的爱好者呢称为COW(母牛),而被反对者称为“疯牛”)。基于COW的字符串内部使用引用记数,在多线程环境下,这样的字符串要么不能用,要么如果库支持多线程的话,慢的无法接受,即使用在你的单线程部分。二者必居其一。 COW字符串更多的问题出现在动态装载库的程序中——如果你释放一个库,有可能你的程序还保留有原来分配在库内存空间字符串的浅拷贝。 对COW字符串带来问题的广泛讨论使得大多数STL实现不采用COW,而对它们basic_string实现使用其他优化策略。然而,“大多数”不是“全部”,所以程序里如果同时用到线程和basic_string,你必须使用非COW实现,后果是放弃你代码对STL其他部分的通用性。 此外,COW有它的优点而且对大量的应用程序非常有用。那么。是否你能够只用在某个应用中选择使用某个字符串的优化方案?“这里我需要非COW字符串并对小于16个字符的字符串执行小字符串优化”或者“这里我要用到COW的优点,而且我要在我自己的堆里分配字符串”。 怎样在200行代码里实现basic_string 放弃现有的多个有用的字符串实现而仅仅自己去开发一个是个艰巨的任务。写出可供具体实现去选择的所有的成员函数和类型定义已经不是个简单的工作。我知道这点是因为我做过写一个在需要使用COM字符串分配和多线程环境下的basic_string的接口。 当我小心翼翼地写好标准规定的所有功能性函数时,我注意到一个有趣的现象。大多数函数看上去都是围绕一小部分核心函数和类型——换句话说,你能够把basic_string接口分解为“核心”和“附加功能”。附加功能部分不管你选择的实现策略如何都是相同的,但是核心部分在不同实现中区别极大。比如说,replace系列函数包含很多功能函数,这些功能函数使用核心函数resize实现。 现在可以用到橡皮擦了,Basic_string的附加功能部分也是最大的部分(在我的实现中有超过300行代码)。反之,写一个实现核心,即使是一个复杂的核心,是个相对简单的工作——在我实现里是75到250行代码。这意味着你只需要简单地在扩展功能部分接口下配以不同的核心实现就可以能够创建一个新的实现。(实际上不完全是这样,因为你可以下载本篇代码,这些代码也希望被使用)实际上,离你梦想中的basic_string实现还差200行代码! 一个基于策略的字符串 你们读过我的书[3]的人(啊呀,你讨厌广告)了解我们设计刚开始就在声嘶力竭叫喊的词:策略(policies)!当然罗,如果你想要分离出一个类实现的特定方面并且让你的用户选择使用那个方面的某个实现,你把那个方面移至一个模板参数并为它定义一个接口。这不是尖端科学,但它也极其有效。 标准的basic_string是这样的: namespace std { template <class E,
|