C++ Boost

既存のグラフから BGL への乗り換え方法

BGL の主要な目標は、新しいアプリケーションやグラフ・アルゴリズムの開発の助けとなる事であるが、BGL アルゴリズムを使用する事で有益となるような、かなり多数の既存コードがある。既存のグラフ・データ構造から BGL アルゴリズムを使用するひとつの方法は、(既存の)古いグラフフォーマットから BGL アルゴリズムで利用できる BGL のグラフへデータをコピーする事である。この手法に関する問題は、コピーを作成することが高価であるということだ。別の手法は、既存のグラフ・データ構造を BGL インタフェースでラップすることである。

アダプタ・パターン [12] は、望まれるインタフェースを提供するための新しいクラス内部に含まれているアダプタ・オブジェクトを典型的に必要とする。  この包含は BGL インタフェースが単にフリー(グローバル)関数から構成されているため、BGL インタフェースでラップする場合には必要では無い。新しいグラフのクラスを作成する代わりに、インタフェースで必要とされる全てのフリー関数の実装を上書きすれば良い。私たちは、このようなフリー関数によるラップ方法を external adaptation (外部適合)と呼んでいる。

最も一般的に使用されているグラフ・クラスのうちの1つに、 LEDA パラメタライズド・グラフ・クラスがある[22]。この節では、このクラスに対して、どのように BGL インタフェースを作成していくかを示すことにする。 最初の問題は、どの BGL インタフェース(或いはコンセプト)を実装すべきであるか?ということである。以下に続く LEDA の冒頭にあるコンセプト: VertexListGraph, BidirectionalGraph, MutableGraph を実装することは、率直で簡単である。

BGL グラフ・クラスに関連する全ての型は、 graph_traits (グラフ特性)クラスを通じてアクセスされる。この特性クラスを LEDA グラフ・クラスのために以下のようなやり方で部分特殊化することができる。この "node""edge" は LEDA における頂点と辺を表現する型である。LEDA グラフでは有向グラフのみ扱えるため、 directed_category に対しては directed_tag を選択する。LEDA グラフは平行な辺の挿入を拒む仕様ではないため、edge_parallel_category に対しては allow_parallel_edge_tag を選択する。LEDA の関数 number_of_nodes()number_of_edges() の戻り値は int であるため、グラフの vertices_size_typeedges_size_type に int を選択する。イテレータ型はもっと複雑なので、後回しにする。

namespace boost {
  template <class vtype, class etype>
  struct graph_traits< GRAPH<vtype,etype> > {
    typedef node vertex_descriptor;
    typedef edge edge_descriptor;

    // iterator typedefs...

    typedef directed_tag directed_category;
    typedef allow_parallel_edge_tag edge_parallel_category;
    typedef int vertices_size_type;
    typedef int edges_size_type;
  };
} // namespace boost

最初に VertexListGraph コンセプトの一部分である IncidenceGraph コンセプトの source() 関数と target() 関数を書くことにしよう。グラフ・パラメータとして LEDA グラフ型を使用し、辺パラメータと頂点の戻り値を特殊化(明示)するために  graph_traits (グラフ特性)を使用する。ここで LEDA の型である "node""edge" を利用することもできるが、より良い練習のために graph_traits (グラフ特性)を利用することにしよう。もし関連する頂点や辺の型を変更する必要性が生じたときに、あなたが書いたコード全体を修正する代わりに graph_traits (グラフ特性)の内部における特殊化(明示)部分の一箇所を修正するだけで済むだろう。LEDA は source() 関数と target() 関数を実装しているので、あとは単にそれらの関数を呼び出せばよい。

namespace boost {
  template <class vtype, class etype>
  typename graph_traits< GRAPH<vtype,etype> >::vertex_descriptor
  source(
    typename graph_traits< GRAPH<vtype,etype> >::edge_descriptor e,
    const GRAPH<vtype,etype>& g)
  {
    return source(e);
  }

  template <class vtype, class etype>
  typename graph_traits< GRAPH<vtype,etype> >::vertex_descriptor
  target(
    typename graph_traits< GRAPH<vtype,etype> >::edge_descriptor e,
    const GRAPH<vtype,etype>& g)
  {
    return target(e);
  }
} // namespace boost

次に実装すべき関数は、IncidenceGraph コンセプトの out_edges() である。この関数は出辺ペアのイテレータを返す。LEDA では STL スタイルのイテレータを使用できないので、いくらか実装を加える必要がある。iterator_adaptor と呼ばれるイテレータを実装するには非常に手軽なユーティリィティがある。イテレータ・クラスを標準の動作と一致させるように書くことは確かに困難であり、あたなが想像している以上に厳しいことであるが、 iterator_adaptor クラスを利用すれば、ポリシー・クラスを実装するだけで、後は iterator_adaptor クラスが面倒を引き受けてくれる。以下に続くコードは、出辺イテレータのためのポリシー・クラスである。LEDA では、辺オブジェクト自身がイテレータのように使用されるが、それは Succ_Adj_Edge() 関数と Pred_Adj_Edge() 関数で、次(後続)や前(先行)の辺へと移動する関数である。 LEDA の辺をイテレータとして使用するにあたって、単に辺オブジェクトを返す dereference() 関数に起因する1つの了解事項であるが、標準イテレータに準拠するための参照外しオペレータは、const メンバ関数(辺の引数も const )でなければならない。しかしながら、戻り値は const であってはならないので、戻り値を const_cast する必要がある。

namespace boost {
  struct out_edge_iterator_policies
  {
    static void increment(edge& e)
    { e = Succ_Adj_Edge(e,0); }

    static void decrement(edge& e)
    { e = Pred_Adj_Edge(e,0); }

    template <class Reference>
    static Reference dereference(type<Reference>, const edge& e)
    { return const_cast<Reference>(e); }

    static bool equal(const edge& x, const edge& y)
    { return x == y; }
  };
} // namespace boost

それでは graph_traits (グラフ特性)クラスに戻ろう!そして out_edge_iteratoriterator_adaptor を実装しよう。わたしたちは、イテレータに関連する全ての型(分類と値型を含む)を特殊化(明示)するためにイテレータ型を使用する。

  namespace boost {
    template <class vtype, class etype>
    struct graph_traits< GRAPH<vtype,etype> > {
      // ...
      typedef iterator_adaptor<edge,
        out_edge_iterator_policies, 
        iterator<std::bidirectional_iterator_tag,edge>
      > out_edge_iterator;
      // ...
    };
  } // namespace boost

graph_traits (グラフ特性)による out_edge_iterator の定義を完了したので、 out_edges() 関数を定義する準備が整った。以下に続く定義は一見すると複雑に思えるが、実際は至って単純である。戻り値は出辺ペアのイテレータであるので、出辺イテレータ型にアクセスするために std::pair を使用し、そして graph_traits (グラフ特性)を使用する。 out_edges() 関数本体において、最初の隣接辺を通過するときに利用されるイテレータ begin のために出辺イテレータを構築し、イテレータ end のために "0" (LEDA にて終端をあらわす標識として使用される)イテレータを構築する。First_Adj_Edge  に対する引数 "0" は、LEDA に(入辺ではなく)出辺を必要としていることを知らせる。

namespace boost {
  template <class vtype, class etype>
  inline std::pair<
    typename graph_traits< GRAPH<vtype,etype> >::out_edge_iterator,
    typename graph_traits< GRAPH<vtype,etype> >::out_edge_iterator >  
  out_edges(
    typename graph_traits< GRAPH<vtype,etype> >::vertex_descriptor u, 
    const GRAPH<vtype,etype>& g)
  {
    typedef typename graph_traits< GRAPH<vtype,etype> >
      ::out_edge_iterator Iter;
    return std::make_pair( Iter(First_Adj_Edge(u,0)), Iter(0) );
  }
} // namespace boost

残されたイテレータ型とインタフェース関数も同じテクニックを使って構築された。LEDA ラッパ・インタフェースの完成したコードは boost/graph/leda_graph.hpp にある。以下のコードでは、BGL インタフェースを正しく実装されたかどうかを確かめるために BGL コンセプト・チェックを使用している。これらのチェックは実装に関する実行時動作テストを行わない。これらのチェックは test/graph.cpp でテストされる。.

  #include <boost/graph/graph_concepts.hpp>
  #include <boost/graph/leda_graph.hpp>

  int
  main(int,char*[])
  {
    typedef GRAPH<int,int> Graph;
    function_requires< VertexListGraphConcept<Graph> >();
    function_requires< BidirectionalGraphConcept<Graph> >();
    function_requires< MutableGraphConcept<Graph> >();
    return 0;
  }


Copyright © 2000-2001 Jeremy Siek, Indiana University (jsiek@osl.iu.edu)
Lie-Quan Lee, Indiana University (llee@cs.indiana.edu)
Andrew Lumsdaine, Indiana University (lums@osl.iu.edu)

Japanese Translation Copyright (C) 2003 OKI Miyuki
オリジナルの、及びこの著作権表示が全ての複製の中に現れる限り、この文書の複製、利用、変更、販売そして配布を認める。このドキュメントは「あるがまま」に提供されており、いかなる明示的、暗黙的保証も行わない。また、いかなる目的に対しても、その利用が適していることを関知しない。