|
泛型 :基于策略的basic_string实现(3) class Storage = AllocatorStringStorage<E, A> > class flex_string : private Storage { public: typedef typename Storage::iterator iterator; typedef typename Storage::const_iterator const_iterator; //21.3.1 构造/拷贝/析构 eXPlicit flex_string(const A& a = A()) : Storage(a) {} .. .. }; 实现Storage策略 现在开始干脏活了。我们来完成Storage的实现。一个高效的字符串实现会保存指向缓存的指针。接下来缓存包含字符串长度和容积,再加字符串本身。为了避免两次分配内存(一次为了记录控制数据一次为了数据)你可能会使用一个叫“the strUCt hack”的技巧,缓存中包含一个C风格字符数组作为它最后一个成员,并且当需要许多字符时会动态增长。这就是SimpleStringStorage所做的 template <class E, class A = std::allocator<E> > class SimpleStringStorage { struct Data { E* pEnd_; E* pEndofMem_; E buffer_[1]; }; Data* pData_; public: size_type size() const { return pData_->pEnd_ - pData_->biffer_; } size_type capacity() const { return pData_->pEndOfMem_ - pData_->buffer_; } .. .. }; pEnd_指向字符串末尾,pEndOfMem_指向已分配缓存的末尾,buffer_大小扩展为能容纳字符串中所有字符——换句话说,buffer_在Data的内存范围外继续存在。为了取得这样的灵活性,pData_不是真的指向一个Data对象,而是指向一个转换为Data的大块内存。这个“struct hack”技巧理论上不是百分之百通用,但实际上是这样。 SimpleStringStorage有另一个小小的特别优化——所有空字符串被一个静态Data实例所共享。另一个实现可能会对空字符串把pData_初始化为空,但那样会在许多成员函数里需要测试是否为空。 SimpleStringStorage 是“简单的”是因为它没有用传入的分配器。SimpleStringStorage需要内存时只是使用标准的自由存储区(new/delete)。使用传入的分配器来分配Data对象比想象中要难,这部分是因为分配器的设计问题(不支持任意大小的对象),部分是因为编译器的兼容性问题。你可以在作为Storage策略的正确实现的AllocatorStringStorage模板类中发现这种做法。 还有种可能的字符串存储实现是简单地使用std::vector作为后端。这种实现方法非常迅速有力,你得到的是一个复用了设计完备的标准库库工具的简单字符串。这也对减少代码量有帮助。你可以在VectorStringStorage中看到这种实现。 所有这三种实现在任何可能的地方都自然而然地用到了EBO(空基类优化Empty Base Optimization)[4]。(我是不是说过“工业级强度”这个时髦词?)使用EBO非常有效率,因为大多数分配器都是空类。
|