如何设计 C++ STL 风格容器

STL标准库所提供的容器已经足够大多数的应用, 而且足够地稳定。可是当咱们须要造点轮子的话,相比起另起炉灶, 能与现有的C++语义和容器兼容更加好. 我总结了一下一个STL-style container所应作的事情.c++

假设咱们的容器类是:git

template<
    typename T,
    typename Allocator = std::allocator<T>
> class my_container;

代码风格

不少人表达过对STL代码风格的不满(全都是下划线), 至于libstdc++里满屏满屏的__gnu_cxx_****简直就是在防盗版. 不过也单独拿出来讲一说, 而且拿其它的风格对比一下:github

  • 保护型别用_St, 大多公用型别带有some_type后缀.算法

  • 模板中的参数名用CamelCase, 文档中称为 Concept .安全

  • 保护成员变量和函数用_M_var_or_func. Boost和Google的代码风格建议用var_or_func_.数据结构

  • __参数和局部变量__命名 __arg_or_local 都绝不留情地用两条下划线开头. Boost不建议用下划线.并发

Operations (操做)

对于数据结构的一些操做, 你应该设计相应的运算子(实际上是语法糖)令程序更可读:app

  • 一个好的容器应该起码要有重分配的操做, assignoperator=ide

  • 对于一些随机存取容器, at 对应 operator[], 可是当不存在时, at不会分配新元素而operator[]会.函数

  • 对于一些线性表, push_backpush 能够对应 operator+=.(可是并无哪一个容器这么作过, 为何?)

  • 比较运算符operator==,!=,<,<=,>,>=, 对于元素类型知足EqualityComparable等概念的容器, 逐个元素迭代逐个元素比较.

而后是一些关于操做的约定命名:

  • 你不该该用 deleteremove 來代替 erase. erase 应该返回新的迭代器以告知用户旧的迭代器失效.

  • 你不该该用 isempty(用is表示它是个谓词) 來代替 empty(虽然这会带来歧义).

  • 你不该该用 empty 來代替 clear.

  • 你不该该用 first(更多用于元组)或 head 來代替 front

  • 你不该该用 lasttail 來代替 back

  • 你不该该用 search(std::search是搜寻一串连续元素时用的) 來代替 find, 并在找不到时返回end()(rfind返回rend())

而且你应该尽可能实现这些操做.

Iterator (迭代器)

迭代器用于遍历, 这是一个容器所必要的. 一个容器的迭代器至少应该有begin()end()两个接口來获__第一个元素__以及所谓__最后一个元素的后继元素__的迭代器, 一般还应该有cbegin()cend()來保证获取到常量版本. 迭代器的分类若是是BidirectionalIterator, 你还应该实现反向迭代器rbegin(), rend()和它们的常量版本. 事实上, STL (的一些实现) 很是依赖迭代器, 如vector的operator[], 甚至将定位彻底交给迭代器的算法去解决:

// class vector { ... public:
reference operator[](size_type __n) { return *(begin() + __n); }

迭代器自己应该继承自std::iterator以保证5个特性(Trait)完整. 留意咱们若是要获取一个容器的迭代器特性, 不该该直接 my_iterator::iterator_category, 由于在一些实现中my_iterator多是vanilla pointer, 而应该 std::iterator_traits<my_iterator>::iterator_category, 这有助于减慢编译器的编译速度和增长代码长度秀逼格(误):

  • iterator_category: 迭代器分类, 指定一个iterator_tag. 用于在迭代的时候使用__更合适的重载函数__.

  • value_type

  • pointer

  • reference

  • difference_type: 这应当是一个数值类型, 当你调用i += nn的类型.

当你要为你的BidirectionalIterator实现反向迭代器的时候, 大可没必要亲自动手造轮子. 利用标准库提供的__适配器__, 你只须要简单地令:

typedef std::reverse_iterator<my_iterator> reverse_iterator;
reverse_iterator rbegin() { return reverse_iterator(end()); }

Allocator (分配器)

或许你在教科书上得知, 你能够在constructor里new一段内存, 而且在destructor里delete这段内存, 而且深深为面向对象的优雅所折服, 认为这是最好的作法. 然而更好的作法是, 咱们把分配内存的工做从类的设计中独立出来, 交给这个叫分配器的东西帮咱们处理.

一个容器最基本的功能就是储存功能, 因此容器的基本特性, 事实上就是帮助管理空间的分配器的基本特性. 分配器有7个由T生成的7个特性, 能够用allocator_traits获取. 例如容器的型别咱们应该这样定义:

typedef std::allocator_traits<allocator_type>::const_pointer const_pointer;
typedef std::allocator_traits<allocator_type>::const_reference const_reference;

关于更多的Traits这里不详述, 它主要目的是经过模板__偏特化__(又叫部分特例化, partial specialization)来同时为C时代就有的传统类型和class提供__统一的接口__为程序编写提供特性信息.

C++的分配器是无状态的, 它只有分配功能帮你省去各类烦人的类型转换和类型问题, 而不能代替smart pointer之类的进行安全的内存管理. 你在管理内存的过程当中, 你应该至少这样作并不限于 (分配器的具体接口参考标准库文档):

  • p = allocator_.allocate(n) 而不是 p = static_cast<value_type*> (operator new (n * sizeof(value_type))).

  • allocator_.construct(p) 而不是 p = new(p) value_type.

C++11

C++11 新增了一些特性, 一个现代的容器应该与标准保持兼容.

initializer_list

在构造函数里实现初始化列表可使你的容器拥有相似这样优美的语义:

my_container<int> cont{1, 3, 5, 7};
my_container<pair<int, int>> = {{3, 4}, {5, 6}};

// 要作到这一点只须要实现这样的接口:
explicit my_container(std::initializer_list<value_type> l) { /*...*/ }

你能够在相似于 append() , assign() 等全部你认为合适的地方也实现这个接口.

R-value Reference

移动语义解决了饱受诟病的右值构造的浪费问题. 你须要在构造函数, assign(), operator=() 实现右值引用版本的接口.

my_container(const self_type& other) { /* copy other here */ }
// 除了经典的复制构造函数之外, 你还应该实现右值引用版本的重载
my_container(self_type&& other) { /* swap pointers */ }

转发解决了参数传递时屡次构造的浪费问题, 这可能在 insert(), push_back()之类的函数中用到. 好比下面, 转发能够令这个vector在整个插入过程当中仅构造了一次.

my_container<vector<int>> cont;
cont.append(vector(1, 2, 3, 4));
cont.append({1, 2, 3, 4});

// 利用模板甚至能够用一个函数含括这两种操做
template <typename ... Args>
void append(Args&& ... args) {
    // ...
    allocator_.construct(p, std::forward<Args>(args)...);
}

Thread Safe

C++11颇有绅士风度地加进了线程库, 而且对STL的线程安全实现提出以下要求:

  • 保证对读同一个容器的并发安全

  • 保证写不一样容器时的并发安全

什么!? 这跟没有线程安全有什么区别? 是的, 然而这并非一个Bug, 而是一个Feature, 这一切都是为了通用库性能考虑.


暂时先写到这, 之后想到什么再加. 本人水平有限, 此文权当抛砖引玉, 欢迎你们指正.

最近居委会,啊不是委员会释出了一份材料:C++ Core Guidelines。你们能够参考参考。

参考资料:

相关文章
相关标签/搜索