便利なC ++列挙子

私たちC ++プログラマーは、間違いなくSTLが大好きです。 実際、それがなければ、多くのことを自分の手で書かなければなりません。 しかし、時にはSTLは痛みと苦痛を引き起こします。 最近、2つのイテレータを最初と最後に取るという標準アルゴリズムに典型的な決定が、私の単純なプロジェクトでは不便であるという事実に出会いました。



以下に表示されているのは、プロジェクトの複雑さを克服するための試みであり、コードの主観的な美しさに対する強い欲求の下で行われたものです。



たとえば、タスクは要素を持つコンテナをウォークスルーすることです。 この場合、当然、プログラムの動作は、値に応じて要素ごとに異なります。 かんたん 複雑に思えますが、文字列の通常のベクトルでこの状況を見てみましょう。 このタスクを実行する関数は次のとおりです。



void Action(std::vector<std::string>::const_iterator curr, const std::vector<std::string>::const_iterator &last) { if (curr == last) { return; } // ... }
      
      





しかし、覚えているように、現在の要素の特定の値を使用すると、動作が変更されます。 このような値は多数存在するため、異なる動作のバリアントが存在するため、ループ内でif-elseキャンバスを生成するのではなく、別個の関数を作成します。 以下よりも簡単なこと:



 void ActionTwo(std::vector<std::string>::const_iterator curr, const std::vector<std::string>::const_iterator &last) { if (curr == last) { return; } // ... } void Action(std::vector<std::string>::const_iterator curr, const std::vector<std::string>::const_iterator &last) { if (curr == last) { return; } // ... if (*curr == ANY_VALUE) { ActionTwo(curr + 1, last); } // ... }
      
      





そして、現在の位置の別の値のために、別の動作オプションを実装する必要があります。 続行できないと思うので、これが何につながるかは明らかです。 常に現在の位置と最後の位置を転送する必要があります。 現在の値を後者と比較する必要があるため、クラス関数の共通関数を作成しても問題は解決しません。つまり、std :: vector <std :: string> :: const_iterator&lastを別の定数として作成する必要があります。



そして、LINQとJavaで列挙子イテレーターがどのように便利に作成されたかを思い出しました。この場合、ポインター計算が不要であるため、std :: vector <std :: string> :: const_iterator currの+ =および-=演算子の使用です。 必要なのは、値に応じて動作を変更することにより、コンテナを簡単にバイパスできることだけです。 次に、C ++でのLINQのような列挙子用の自転車を見ていきます。



実験用の.hファイルを作成し、その中にDq名前空間を定義します。これはミニライブラリの名前になります。



 #pragma once namespace Dq { //   : Container  ..Args    STL     . template <template <typename...> typename Container, typename ...Args> //  - class Enumerator { private: //    Container<Args...> &List = Container<Args...>(); //       typename Container<Args...>::iterator Position = List.begin() - 1; public: //             bool MoveNext() { return ++Position != List.end(); } //      typename Container<Args...>::value_type &operator*() const { return *Position; } //        void Reset() { Position = List.begin() - 1; } //      explicit Enumerator(const Container<Args...> &cont) : List(cont) {} }; }
      
      





すでに優れているので、1つの列挙子引数を目的の関数に渡すことができます。 C ++ 17では、このような列挙子の作成は非常に簡単です。



 std::vector values { 0, 1, 2, 3, 4, 5 }; Dq::Enumerator i(values);
      
      





それまでの間、標準は最終的に承認されていません。補助機能を作成します。



 namespace Dq { //       template <template <typename...> typename Container, typename ...Args> Enumerator<Container, Args...> GetEnumerator(const Container<Args...> &cont) { return Enumerator<Container, Args...>(cont); } }
      
      





そして、次のように使用します。



 std::vector<int> values{ 0, 1, 2, 3, 4, 5 }; auto i = Dq::GetEnumerator(values);
      
      





列挙子を使用して、最初の例を書き換える方法を次に示します。



 void ActionTwo(auto &position) { if (!position.MoveNext()) { return; } // ... } void Action(auto &position) { if (!position.MoveNext()) { return; } // ... if (*position == ANY_VALUE) { ActionTwo(); } // ... }
      
      





私にとって、このソリューションは、STLイテレーターを使用するよりもはるかに美しく、日常の作業に便利です。 さて、小さな欠陥については、そのようなイテレーターは標準アルゴリズムには使用できません。 しかし、これは幸いにも簡単に修正でき、Enumeratorクラスにメソッドを追加するだけです。



 template <template <typename...> typename Container, typename ...Args> typename Container<Args...>::iterator Enumerator<Container, Args...>::CurrentPostion() const { return Position; }
      
      





そして



 template <template <typename...> typename Container, typename ...Args> typename Container<Args...>::iterator Enumerator<Container, Args...>::LastPostion() const { return List.end(); }
      
      





すべてのコードはGitHubの1つのファイルで見ることができます。



要約すると:



これで、STLアルゴリズムと互換性のある標準イテレータ用の便利なラッパー列挙子ができました。 人生はもう少し楽しくなり、世界のエントロピーは記事を書くコストのために増えました。私の自転車の客観的な有用性を判断すべきです。



All Articles