読者です 読者をやめる 読者になる 読者になる

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());
}