如何設計 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。你們能夠參考參考。

參考資料:

相關文章
相關標籤/搜索