3

次の (単純化された) シナリオを検討してください。

class edgeOne {
  private:
    ...
  public:
    int startNode();
    int endNode();
};

class containerOne {
  private:
    std::vector<edgeOne> _edges;
  public:
    std::vector<edgeOne>::const_iterator edgesBegin(){
      return _edges.begin();
    };
    std::vector<edgeOne>::const_iterator edgesEnd(){
      return _edges.end();
    };
};

class edgeTwo {
  private:
    ...
  public:
    int startNode();
    int endNode();
};

class containerTwo {
  private:
    std::vector<edgeTwo> _edges;
  public:
    std::vector<edgeTwo>::const_iterator edgesBegin(){
      return _edges.begin();
    };
    std::vector<edgeTwo>::const_iterator edgesEnd(){
      return _edges.end();
    };
};

つまり、2 つのほぼ同一のエッジ タイプと 2 つのほぼ同一のコンテナ タイプがあります。各種類を個別に反復できます。これまでのところ、とても順調です。

しかし、私のユースケースは次のとおりです。いくつかの基準に基づいて、containerOne または containerTwo オブジェクトを取得します。エッジを反復処理する必要があります。しかし、タイプが異なるため、コードの複製なしでは簡単に行うことはできません。

std::vector<edgeOne>::const_iteratorしたがって、私の考えは次のとおりです。次のプロパティを持つイテレータstd::vector<edgeTwo>::const_iteratorが必要です。-const edgeOne &またはを返す代わりにconst edgeTwo &operator*を返す必要がstd::pair<int,int>あります。つまり、変換を適用します。

特にBoost.Iterator Libraryを見つけました:

  • iterator_facade は、標準に準拠したイテレータを構築するのに役立ちます。
  • edgeOnetransform_iterator は、 transformおよびedgeTwotoに使用できますがstd::pair<int,int>、完全なソリューションがどのように見えるかは完全にはわかりません。イテレータのほぼ全体を自分で作成した場合、transform_iterator を使用する利点はありますか?それとも、ソリューションがより重くなるだけでしょうか?

イテレータは次のデータのみを保存する必要があると思います:

  • 値のタイプが edgeOne か edgeTwo かを示すフラグ (現時点では bool で十分ですが、必要に応じて enum 値の方が簡単に拡張できます)。
  • 両方のイテレータ タイプのエントリを持つunion(フラグに一致するものだけがアクセスされる)。

それ以外は、オンザフライで計算できます。

このポリモーフィック動作に対する既存の解​​決策、つまり同じ値型を持つ 2 つ (またはそれ以上) の基になるイテレータ実装を組み合わせたイテレータ実装があるのではないかと思います。そのようなものが存在する場合、それを使用して 2 つの を組み合わせることができますtransform_iterator

ディスパッチ (つまり、containerOne または containerTwo オブジェクトにアクセスする必要があるかどうかの決定) は、独立した関数によって簡単に実行できます ...

この問題に関するご意見やご提案はありますか?

4

3 に答える 3

3

edgeOne と edgeTwo をポリモーフィックにするのはどうですか? コンテナでポインタを使用していますか?

class edge
class edgeOne : public edge
class edgeTwo : public edge

std::vector<edge*>
于 2013-07-18T11:06:40.797 に答える
2

いくつかの基準に基づいて、containerOne または containerTwo オブジェクトを取得します。エッジを反復処理する必要があります。しかし、タイプが異なるため、コードの複製なしでは簡単に行うことはできません。

「コードの複製」とは、ソース コードまたはオブジェクト コードのことですか? 単純なテンプレートを超える理由はありますか? 「コードの重複」を構成する 2 つのテンプレートのインスタンス化が心配な場合は、ほとんどの処理を、テンプレート化されていないdo_whatever_with(int, int)サポート関数の枠外に移動できます。

template <typename Container>
void iterator_and_process_edges(const Container& c)
{
    for (auto i = c.edgesBegin(); i != c.edgesEnd(); ++i)
        do_whatever_with(i->startNode(), i->endNode());
}

if (criteria)
    iterate_and_process_edges(getContainerOne());
else
    iterate_and_process_edges(getContainerTwo());

私の本来の目的は、開始ノードと終了ノードにアクセスする必要があるコードからディスパッチ機能を隠すことでした。ディスパッチは一般的ですが、ループ内で発生することは一般的ではないため、IMO は両方を分離する正当な理由です。

私がそこにあなたをフォローしているかどうかはわかりませんが、私は試しています。つまり、「開始ノードと終了ノードにアクセスするだけのコード」です。アクセスによって、コンテナー要素の startNode と endNode を取得することを意味するのか、それともそれらの値を使用することを意味するのかは明らかではありません。私はすでにdo_whatever_withそれらを使用する関数を除外していたので、削除すると、リクエストは Edge からノードを抽出するコードを分離したいように見えます - 以下のファンクターで実行されます:

template <typename Edge>
struct Processor
{
    void operator()(Edge& edge) const
    {
        do_whatever_with(edge.startNode(), edge.endNode());
    }
};

そのファンクターは、コンテナー内の各ノードに適用できます。

template <typename Container, class Processor>
void visit(const Container& c, const Processor& processor)
{
    for (auto i = c.edgesBegin(); i != c.edgesEnd(); ++i)
        processor(*i);
}

「開始ノードと終了ノードにアクセスする必要があるコードからディスパッチ機能を隠す」 - 基準に基づいて、次に反復に基づいて、さまざまなレベルのディスパッチがあるように私には思えます (関数呼び出しのすべてのレイヤーはある意味で「ディスパッチ」)しかし、繰り返しますが、あなたが求めているのは上記の反復の分離だと思います。

if (criteria)
    visit(getContainerOne(), Process<EdgeOne>());
else
    visit(getContainerTwo(), Process<EdgeTwo>());

ディスパッチは一般的ですが、ループ内で発生することは一般的ではないため、IMO は両方を分離する正当な理由です。

私があなたに同意するとは言えませんが、最初のアプローチでメンテナンスの問題が見られるかどうか (私には汚れが単純に見えます - この最新の化身よりもレイヤーが少なく、手間がかなり少ない)、または再利用の可能性が見られるかどうかによって異なります。上記のvisit実装は再利用できるように意図されていますが、単一の for ループ (C++11 を使用している場合はさらに単純化されます) を再利用することは役に立ちません。

このモジュール化に慣れていますか、それとも私があなたのニーズを完全に誤解していますか?

于 2013-07-18T11:15:19.720 に答える
1
template<typename T1, typename T2>
boost::variant<T1*, T2*> get_nth( boost::variant< std::vector<T1>::iterator, std::vector<T2>::iterator > begin, std::size_t n ) {
  // get either a T1 or a T2 from whichever vector you actually have a pointer to
}

// implement this using boost::iterator utilities, I think fascade might be the right one
// This takes a function that takes an index, and returns the nth element.  It compares for
// equality based on index, and moves position based on index:
template<typename Lambda>
struct generator_iterator {
  std::size_t index;
  Lambda f;
};
template<typename Lambda>
generator_iterator< typename std::decay<Lambda>::type > make_generator_iterator( Lambda&&, std::size_t index=0 );

boost::variant< it1, it2 > begin; // set this to either one of the begins
auto double_begin = make_generator_iterator( [begin](std::size_t n){return get_nth( begin, n );} );
auto double_end = double_begin + number_of_elements; // where number_of_elements is how big the container we are type-erasing

これで、一方または他方を反復処理して を返すイテレータができましたboost::variant<T1*, T2*>

variant次に、ビジターを使用して返された から必要な 2 つのフィールドを抽出し、それを ADT のように扱うヘルパー関数を作成できます。ADT が嫌いな場合は、代わりに をラップしvariantてメソッドを提供するクラスを作成するか、 をあまり一般的でないように変更して、代わりに生成済みのデータでget_nthを返すこともできます。struct

アクセスごとに分岐に相当するものがありますがvirtual、この計画には関数のオーバーヘッドはありません。現在、auto型付き変数が必要ですが、明示的なファンクターを記述してラムダを置き換えることができ、[begin](std::size_t n){return get_nth( begin, n );}その問題も解消されます。

より簡単な解決策:

for_each各コンテナーを反復処理し、処理されたデータを渡された関数に渡す関数を作成します。

struct simple_data { int x,y; };
std::function<std::function<void(simple_data)>> for_each() const {
  auto begin = _edges.begin();
  auto end = _edges.end();
  return [begin,end](std::function<void(simple_data)> f) {
    for(auto it = begin; it != end; ++it) {
      simple_data d;
      d.x = it->getX();
      d.y = it->getY();
      f(d);
    }
  };
};

他にも同様です。を呼び出すことで、詳細を気にせずにコンテンツを繰り返し処理できるようになりました。直接行うのではなく、戻り値にfoo->foreach()( [&](simple_data d) { /*code*/ } );を詰め込んだため、ループの概念を別の関数に渡すことができます。foreachstd::function

コメントで述べたように、他の解決策には、boostの型消去されたイテレータ ラッパーを使用すること、またはsimple_data. ブースト イテレータ作成関数を直接使用して、イテレータ over を作成することもできますboost::variant<T1, T2>

于 2013-07-18T12:52:57.277 に答える