C ++のイディオム。 静的訪問者

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(){} }; //////////////////////////////////////////////////////////////////////////// class regular_file_t : public abstract_file_t { public: std::string name() const; void accept(visitor_t& v); size_t size(); }; //////////////////////////////////////////////////////////////////////////// typedef std::vector<abstract_file_t*> file_vector_t; class directory_t : public abstract_file_t { public: void accept(visitor_t& v); std::string name() const; file_vector_t& files(); };
      
      





ご覧のとおり、ファイルシステムオブジェクトがどのように機能するかについての知識は、ベースの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_; }; //////////////////////////////////////////////////////////////////////////////// struct person_visitor_t { person_visitor_t(size_t age_limit) : age_limit_(age_limit){} bool operator()(const person_t& p) {return visit(p);} bool visit(const person_t& p) {return p.age() < age_limit_;} size_t age_limit_; }; //////////////////////////////////////////////////////////////////////////////// int main() { std::vector<person_t> person_vec; person_vec.push_back(person_t("Person 1", 43)); person_vec.push_back(person_t("Person 2", 20)); auto it = std::find_if( person_vec.begin(), person_vec.end(), person_visitor_t(30)); if(it != person_vec.end()) std::cout << it->age() << std::endl; return 0; }
      
      





最初の章で見たものと非常に似ていますよね?



使用例



ブーストグラフライブラリ


述語のアイデアを発展させることができます。 ユーザーが提供する「訪問者」の助けを借りて、いくつかの重要なポイントでユーザーにアルゴリズムの動作を変更させてみませんか? ノードとエッジを保存するためのデータ構造と、これらの構造を処理するためのアルゴリズムで構成されるグラフを操作するためのライブラリ( Boost Graph Library )を作成するとします。 最大限の柔軟性を得るために、各アルゴリズムの2つのバリエーションを提供できます。 1つはデフォルトのアクションを実行し、もう1つはユーザーがアルゴリズムのいくつかのステップに影響を与えることを可能にします。 簡略化すると、これは次のように表すことができます。



 template<typename T> struct node_t { node_t(){} //   accept template<typename V> void on_init(V& v) {v.on_init(t_);} //   accept template<typename V> void on_print(V& v) {v.on_print(t_);} T 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); } } //////////////////////////////////////////////////////////////////////////////// template<typename Graph> void print_graph(Graph& g); template<typename Graph, typename Visitor> void print_graph(Graph& g, Visitor& v) { for(size_t i = 0; i < g.size(); ++i) { g[i].on_print(v); } }
      
      





これでユーザーコード。



 struct person_t { std::string name; int age; }; //////////////////////////////////////////////////////////////////////////////// // visitor struct person_visitor_t { // visit() void on_init(person_t& p) { p.name = "unknown"; p.age = 0; } // visit() void on_print(const person_t& p) { std::cout << p.name << ", " << p.age << std::endl; } }; //////////////////////////////////////////////////////////////////////////////// int main() { person_visitor_t person_visitor; typedef std::vector<node_t<person_t> > person_vec_t; person_vec_t graph; generate_graph<person_t>(graph, person_visitor, 10); print_graph(graph, person_visitor); }
      
      





バラリアン


静的訪問者イディオムを使用する別の非常に興味深い例は、 boost :: variantにあります。 Variantは静的に型指定されたunionです。 有効なタイプのデータはすべて同じバイト配列に格納されます。 そして、実際には、常にこの配列を常にバリアント内に格納した「訪問」しますが、異なるタイプの観点から毎回それを「見ます」。 これを何らかの方法で実装できます(コードはできる限りシンプルで、主なアイデアのみを伝えます)。



  template< typename T1 = default_param1, typename T2 = default_param2, typename T3 = default_param3 > class variant { ... public: //     accept() template<typename Visitor> void apply_visitor(const Visitor& v) { switch(type_tag_) //       { case 1: apply1(v, T1()); break; case 2: apply2(v, T2()); break; case 3: apply3(v, T3()); break; default: break; } } };
      
      





適用関数は次のようになります



  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) { // data_ -  . //   visit()  operator() v(*(T1*)(&data_[0])); } //     . 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());
      
      






All Articles