Visitorパターンは、データ処理アルゴリズムをデータ自体から分離する別の方法を提供します。 この記事では、元のパターンの背後にあるアイデアとそのC ++固有のバリエーションを簡単に説明し、いくつかの簡単な使用例を示します。
訪問者
最初に、古典的な
Visitorがどのように機能するかを思い出しましょう。 このパターンの動機は非常に単純です。 プログラムで、ポリモーフィックポインタのコンテナ(ツリー、グラフ)を処理し、オブジェクトごとに一連の操作を実行する必要があり、このセットは特定のタイプごとに異なる必要があることを想像してください。 また、オブジェクト自体は、ハンドラーが「訪問」できることを除いて、オブジェクトを処理するアルゴリズムについて何も知る必要がないことに注意する価値があります。
たとえば、ファイルシステムオブジェクト:ファイル、フォルダー:
class abstract_file_t { public: virtual std::string name() const = 0; virtual void accept(visitor_t& v) = 0; virtual ~abstract_file_t(){} };
ご覧のとおり、ファイルシステムオブジェクトがどのように機能するかについての知識は、ベースの
visitor_tタイプのオブジェクトが
それらを 「訪問」できるという事実のみにあり
ます 。
accept関数では、単に「訪問者を入れる」だけです。
void regular_file_t::accept(visitor_t& v) {v.visit(*this);}
ディレクトリの場合、コードを追加して、その中のすべてのファイルを「訪問」することができます。
「訪問者」は次のように配置されます。
class visitor_t { public: virtual void visit(regular_file_t& f) = 0; virtual void visit(directory_t& dir) = 0; virtual ~visitor_t(){} }; class print_info_visitor_t : public visitor_t { public: void visit(regular_file_t& f); { std::cout << "visiting concrete file. file name: " << f.name() << " file size: " << f.size() << std::endl; } void visit(directory_t& dir) { std::cout << "visiting directory. directory name: " << dir.name() << ". contains " << dir.files().size() << “files” << std::endl; } };
静的訪問者
静的訪問者の本質は、このデータを処理するアルゴリズムからデータを分離することにもあります。 主な違いは、古典的な
Visitorの動的なポリモーフィズムが静的に置き換えられていることです(したがって、イディオムの名前)。
STLアルゴリズムを使用する場合、ほぼ毎回このパターンの1つの実装に遭遇します。 実際、
STL述語は
静的訪問者の良い例です。 これを明確にするために、次の小さな例を検討してください。
class person_t { public: person_t(const std::string& name, size_t age) : name_(name), age_(age){} template<typename Visitor> void accept(Visitor& v) {v.visit(*this);} size_t age() const {return age_;} private: std::string name_; size_t age_; };
最初の章で見たものと非常に似ていますよね?
使用例
ブーストグラフライブラリ
述語のアイデアを発展させることができます。 ユーザーが提供する「訪問者」の助けを借りて、いくつかの重要なポイントでユーザーにアルゴリズムの動作を変更させてみませんか? ノードとエッジを保存するためのデータ構造と、これらの構造を処理するためのアルゴリズムで構成されるグラフを操作するためのライブラリ(
Boost Graph Library )を作成するとします。 最大限の柔軟性を得るために、各アルゴリズムの2つのバリエーションを提供できます。 1つはデフォルトのアクションを実行し、もう1つはユーザーがアルゴリズムのいくつかのステップに影響を与えることを可能にします。 簡略化すると、これは次のように表すことができます。
template<typename T> struct node_t { node_t(){}
アルゴリズム 1つのデフォルトバージョンと1つの訪問者を使用
template<typename T, typename Graph> void generate_graph(Graph& g, size_t size); template<typename T, typename Graph, typename Visitor> void generate_graph(Graph& g, Visitor& v, size_t size) { for(size_t i = 0; i < size; ++i) { node_t<T> node; node.on_init(v); g.push_back(node); } }
これでユーザーコード。
struct person_t { std::string name; int age; };
バラリアン
静的訪問者イディオムを使用する別の非常に興味深い例は、
boost :: variantにあります。
Variantは静的に型指定された
unionです。 有効なタイプのデータはすべて同じバイト配列に格納されます。 そして、実際には、常にこの配列を常に
バリアント内に格納した「訪問」しますが、異なるタイプの観点から毎回それを「見ます」。 これを何らかの方法で実装できます(コードはできる限りシンプルで、主なアイデアのみを伝えます)。
template< typename T1 = default_param1, typename T2 = default_param2, typename T3 = default_param3 > class variant { ... public:
適用関数は次のようになります
template<typename Visitor, typename U> void apply1( const Visitor& v, U u, typename std::enable_if< !std::is_same<U, default_param1>::value>::type* = 0) {
ここでは、デフォルトでSFINAEを使用して、現在のタイプの正しい機能を「有効」にし、クラスパラメーターを「無効」にします。 ユーザーコードは非常に簡単です。
struct visitor_t { void operator()(int i)const ; void operator()(double d)const; void operator()(const std::string& s)const; }; variant<int, double> test_v(34); test_v.apply_visitor(visitor_t());