Hatena::ブログ(Diary)

Life like a clown このページをアンテナに追加 RSSフィード Twitter

2009-10-08

Container Accessors

追記 ここで記述したコードには問題が含まれていたため, Container Wrappers - Life like a clown に補足記事を書きました.

ファイルの解析など何らかの操作を行った結果をコンテナに格納し,そのコンテナから各情報を取得するようなコードを書く機会にしばしば遭遇します.

このとき,まず始めに思いつくのが,対象となる STL コンテナ・クラスを継承して(ファイルの解析用メソッドなど)独自のメソッドのみを定義して拡張するという方法ですが,STL コンテナは継承される事を想定して設計されていないため,(拡張したクラスを使用する)ユーザの利用方法によっては問題が生じる事があります.

"遺伝子 is-a std::vector<Nucleotide>"だよなーと思い、これは継承の使いどころなんじゃないかと思って調べ始めた。基本的に、STLコンテナは継承すべきものではないらしい。STLコンテナが特別ダメということではなく、STLコンテナのように仮想デストラクタを持っていないクラスをpublic継承すると危険な場合がある、と。

404 Page not found - Heavy Watal

そのため,多くの場合は,格納用の STL コンテナをメンバ変数として保持してコンテナへのアクセス・メソッドは自力で定義すると言う方法を取るのですが,やはりこの方法は面倒なように感じられます.

そこで,メンバ変数として持たせた STL コンテナのメソッドを自動で定義してくれるクラスを作成してみました.作成したクラスの宣言は以下の通りです.

namespace clx {
    template <class Type>
    class container_accessor;
    
    template <class Type>
    class random_accessor : public container_accessor<Type>;
    
    template <class Type>
    class vector_accessor : public random_accessor<Type>;
    
    template <class Type>
    class deque_accessor : public random_accessor<Type>;
    
    template <class Type>
    class set_accessor : public container_accessor<Type>;
    
    template <class Type>
    class multimap_accessor : public set_accessor<Type>;
    
    template <class Type>
    class map_accessor : public multimap_accessor<Type>;
}

container_accessor は begin(), end() など全てのコンテナが保持しているメソッドのみを定義しています.また,random_accessor は,vector/deque がともに保持しているメソッドのみを定義しています.それぞれのクラスがどのメソッドを保持しているかは,no title などが参考になります.ただし,今回作成したクラスは,いくつかのメソッドを定義していません.具体的には,swap(),および list における splice(), merge() の引数として自身の(コンテナ)クラスを指定するメソッド群です.

継承関係は以下のようになります.

f:id:tt_clown:20091009000256p:image:w550

ユーザは,STL コンテナ・クラスに対応する accessor クラスの他に,継承元の関係にある accessor クラスも使用することができます.例えば,コンテナとしては std::map を利用するけれども,ユーザに提供するアクセス・メソッドは begin(), end() などだけに留めたい場合は,map_accessor の代わりに container_accessor を利用する事ができます.

これらのクラスの使用例は以下のようになります.まず,テンプレート・引数に使用する STL コンテナのクラスを指定して,対象となる accessor クラスを継承します.そして,コンストラクタに,メンバ変数として保持している STL コンテナ・クラスのインスタンスを指定します.

#include <iostream>
#include <string>
#include <map>
#include "clx/container_accessor.h"

/* ------------------------------------------------------------------------- */
/*
 *  exmap
 *
 *  コンテナに対して行う拡張的なメソッドのみを定義する.
 *  その他の各アクセス・メソッドは,map_accessor を継承する事によって
 *  定義する.
 */
/* ------------------------------------------------------------------------- */
class exmap : public clx::map_accessor<std::map<int, std::string> > {
public:
    typedef std::map<int, std::string> data_type;
    
    exmap() :
        clx::map_accessor<data_type>(v_), v_() {}
    
    virtual ~exmap() throw() {}
    
    void do_something() {
        v_[0] = "Hello, world!";
        v_[1] = "foo";
        v_[2] = "hoge";
        v_[3] = "Bye bye.";
    }
    
private:
    data_type v_;
};

/* ------------------------------------------------------------------------- */
//  main
/* ------------------------------------------------------------------------- */
int main(int argc, char* argv[]) {
    exmap v;
    v.do_something();
    
    // std::map が持っているメソッドを使用する.
    std::cout << "size: " << v.size() << std::endl;
    std::cout << "--" << std::endl;
    for (exmap::iterator pos = v.begin(); pos != v.end(); ++pos) {
        std::cout << pos->first << ": " << pos->second << std::endl;
    }
    std::cout << std::endl;
    
    if (v.find(0) != v.end()) {
        std::cout << "find: " << v[0] << std::endl;
    }
    std::cout << std::endl;
    
    return 0;
}
実行結果
[clown@stinger clx_example]$ ./a
size: 4
--
0: Hello, world!
1: foo
2: hoge
3: Bye bye.

find: Hello, world!

ソースコードは,最新のバージョンにはまだ含まれていないため svn/clxcpp から取得して下さい.

References