STLのqueueとかstackとかが好きになるたった一つの方法
全国1,000,000人のSTLファンのみなさんに朗報です。
STLのqueueとかstackとか使いにくくないですか?
あれって、中身はlistとかqueueとかvectorのくせに使いにくくないですか?
触れるインターフェイスが少なすぎ、とか思ってないですか?
渡したコンテナを触れないときどうしてますか?
1.渡すコンテナを独自で作成し、インターフェイスは実装し中身は気合いでいじる
できますが、queueやstackで渡されるコンテナは「コピー」なので相当醜いことになります。却下。
2.queueやstackをprotected継承してインターフェイスを拡張する
正解!! 移植性はないけど正解!!
移植性がないのはSTLには環境によって独自の実装があるからです。
しかし、多くの場合ちょっと書き換えるだけでこのテクニックが使えます。
これを使うと、
swapができるqueue(シュリンクトゥフィットや、クリアが使える!)
reserveができるstack(最初から必要量を確保したstackにできる!)
なんてものが簡単につくれます。
さてコード。あえて不完全なコードですが、使い方は解ってもらえる筈です。
C++ code
- 56 lines - codepad
ここでおそらく予想されうる反論。
stackやqueueを継承するな!!!
コンテナがcっていうところがそもそも変だ!!!
コンテナがcでなくてprotectedじゃなかったらどうするんだ!!!
ところがどっこい、これはISOのガイドラインで決まっています。
正しくは標準ガイドラインの記述に従ったSTLならば
stackやqueueの中のコンテナはcという名前でprotectedになっています。
OSXについているstl_queueのコメントを引用しましょう
protected: /** * 'c' is the underlying container. Maintainers wondering why * this isn't uglified as per style guidelines should note that * this name is specified in the standard, [23.2.3.1]. (Why? * Presumably for the same reason that it's protected instead * of private: to allow derivation. But none of the other * containers allow for derivation. Odd.) */ _Sequence c;
ご理解いただけましたか?
「'c'は基本的なコンテナだ」
「理由を不思議に思うが、この名前は標準規格[23.2.3.1]で示されているので従っている」
「なぜ?」
というか、このコメントを書いた人も不思議に思っていますね。
ISOのPDFでも確認していただけると早いのですが、
ISO/IEC 14882:2003の[23.2.3.1]ではこのようになっています。
protected:
Container c;
そう、標準のガイドラインで
protected
であり、
c
という名前であること、となっているんです。
故にこの実装は実装の詳細こそ違いますが、このガイドラインに従い、protectedなcという名前のコンテナが実体になります。
(他のSTLでもこのガイドラインに従っているものを見ることができます。これは強制ではないでしょうが、正しい実装に基づけばこの規則は破られません)
なので、ガイドラインに従うSTLの実装ならば常にこのcをprotected継承によって操作できます。
このコメントで
「おそらくqueueやstackは派生を許容するためにprotectedになっています。
しかし、他のコンテナはいずれも派生を考慮していません。変じゃね?」
と書いてありますが、おそらくこのためです。(と俺は考えました)
ということで、
さぁ、どんどんqueueやstackのインターフェイスを拡張しましょう!!
queueやstackを好きになりましょう!
移植性はありませんが今よりもっと好きになれる筈です。
コンテナを直接使えばいいじゃん、は禁句です。
#include <queue> template <class T> class Queue : protected std::queue<T> { public: using std::queue<T>::empty; using std::queue<T>::size; using std::queue<T>::front; using std::queue<T>::back; using std::queue<T>::push; using std::queue<T>::pop; void swap(Queue<T>& q) {using std::swap; swap(this->c, q.c);} }; #include <stack> template <class T> class Stack : protected std::stack<T, std::vector<T> > { public: typedef std::size_t size_type; using std::stack<T, std::vector<T> >::empty; using std::stack<T, std::vector<T> >::size; using std::stack<T, std::vector<T> >::top; using std::stack<T, std::vector<T> >::push; using std::stack<T, std::vector<T> >::pop; void reserve(size_type n) {this->c.reserve(n);} void swap(Stack<T>& s) {using std::swap; swap(this->c, s.c);} }; int main() { Stack<int> s; s.reserve(100); s.push(1); s.push(2); s.pop(); printf("stack size:%ld\n", s.size()); Stack<int>().swap(s); printf("----- swap -----\n"); printf("stack size:%ld\n", s.size()); Queue<int> q; q.push(1); q.push(2); printf("queue size:%ld\n", q.size()); Queue<int>().swap(q); printf("----- swap -----\n"); printf("queue size:%ld\n", q.size()); }