6

ライブラリを作成していて、一連の値を返す必要がある関数がある場合、次のようなことができます。

std::vector<int> get_sequence();

ただし、これには、ライブラリ ユーザーが使用したいコンテナーを使用できるようにするのではなく、std::vector<> コンテナーを使用する必要があります。さらに、パフォーマンスに悪影響を及ぼす可能性のある、返された配列の余分なコピーを追加する可能性があります (コンパイラがこれを最適化できるかどうかによって異なります)。

開始と終了 iter を取るテンプレート化された関数を作成することで、理論的には任意のコンテナーの使用を有効にする (そして不要な余分なコピーを避ける) ことができます。

template<class T_iter> void get_sequence(T_iter begin, T_iter end);

関数は、反復子によって指定された範囲にシーケンス値を格納します。しかし、これの問題は、シーケンスのサイズを知る必要があるため、間に十分な要素がbeginありend、シーケンス内のすべての値を格納する必要があることです。

次のようなインターフェースを考えました。

template<T_insertIter> get_sequence(T_insertIter inserter);

これは、 T_insertIter が挿入反復子 (たとえば、 で作成されたものstd::back_inserter(my_vector)) である必要がありますが、コンパイラは非挿入反復子を喜んで受け入れますが、実行時に正しく動作しないため、これは誤用しやすいようです。

では、任意の長さのシーケンスを返すジェネリック インターフェイスを設計するためのベスト プラクティスはありますか?

4

10 に答える 10

6

get_sequenceforward_iteratorがシーケンスをオンデマンドで生成する (カスタム) クラスを返すようにします。bidirectional_iterator(それがシーケンスにとって実用的であるかのように、より高度なイテレータ型である可能性もあります。)

その後、ユーザーはシーケンスを任意のコンテナー タイプにコピーできます。または、イテレータで直接ループして、コンテナを完全にスキップすることもできます。

ある種の終了イテレータが必要になります。シーケンスを生成する方法を正確に知らなければ、それをどのように実装すべきかを正確に言うのは困難です。1 つの方法は、イテレータ クラスに、次のような終了イテレータを返す静的メンバ関数を持たせることです。

static const my_itr& end() { static const my_itr e(...); return e; };

where...は、終了反復子 (プライベート コンストラクターを使用する場合があります) を作成するために必要なパラメーターを表します。次に、ループは次のようになります。

for (my_itr i = get_sequence(); i != my_itr::end(); ++i) { ... }

連続する整数のシーケンスを生成する前方反復子クラスの簡単な例を次に示します。明らかに、これは双方向またはランダム アクセス イテレータに簡単に変換できますが、例を小さくしたかったのです。

#include <iterator>

class integer_sequence_itr
  : public std::iterator<std::forward_iterator_tag, int>
{
 private:
  int i;

 public:
  explicit integer_sequence_itr(int start) : i(start) {};

  const int& operator*()  const { return i; };
  const int* operator->() const { return &i; };

  integer_sequence_itr& operator++() { ++i; return *this; };
  integer_sequence_itr  operator++(int)
    { integer_sequence_itr copy(*this); ++i; return copy; };

  inline bool operator==(const integer_sequence_itr& rhs) const
    { return i == rhs.i; };

  inline bool operator!=(const integer_sequence_itr& rhs) const
    { return i != rhs.i; };
}; // end integer_sequence_itr

//Example:  Print the integers from 1 to 10.
#include <iostream>

int main()
{
  const integer_sequence_itr stop(11);

  for (integer_sequence_itr i(1); i != stop; ++i)
    std::cout << *i << std::endl;

  return 0;
} // end main
于 2008-09-18T22:23:15.970 に答える
3

エラー...ちょうど私の2セントですが:

void get_sequence(std::vector<int> & p_aInt);

これにより、コピーによる返品の可能性がなくなります。さて、本当にコンテナを押し付けたくない場合は、次のようなことを試すことができます。

template <typename T>
void get_sequence(T & p_aInt)
{
    p_aInt.push_back(25) ; // Or whatever you need to add
}

これは、ベクター、リスト、およびdeque(および同様のコンテナー)に対してのみコンパイルされます。可能なコンテナの大規模なセットが必要な場合、コードは次のようになります。

template <typename T>
void get_sequence(T & p_aInt)
{
    p_aInt.insert(p_aInt.end(), 25) ; // Or whatever you need to add
}

しかし、他の投稿で述べられているように、インターフェースを1種類のコンテナーのみに制限することを受け入れる必要があります。

于 2008-09-18T22:38:56.447 に答える
3

インターフェイスがコンテナに依存しない必要があるのはなぜですか? Scott Meyers は、彼の「Effective STL」で、誘惑がどんなに強くても、コードをコンテナーに依存しないようにしようとしないことの正当な理由を示しています。基本的に、コンテナーはまったく異なる用途を意図しています。出力を map または set に格納したくない (それらは間隔コンテナーではありません) ため、vector、list、および deque が残されます。リストが必要な場所にベクトルを配置するには、またはその逆ですか? それらは完全に異なり、両方を機能させようとするよりも、いずれかの機能をすべて使用する方が良い結果が得られます。「Effective STL」を読んでみてください。時間をかける価値はあります。

ただし、コンテナについて何か知っている場合は、次のようなことを検討してください。


template void get_sequence(T_Container & container)
{
  //...
  container.assign(iter1, iter2);
  //...
}

または多分


template void get_sequence(T_Container & container)
{
  //...
  container.resize(size);
  //use push_back or whatever
  //...
}

または、次のような戦略で何​​をするかを制御することさえできます


class AssignStrategy // for stl
{
public:
  template
  void fill(T_Container & container, T_Container::iterator it1, T_Container::iterator it2){
    container.assign(it1, it2);
  }
};

class ReserveStrategy // for vectors and stuff
{
public:
  template
  void fill(T_Container & container, T_Container::iterator it1, T_Container::iterator it2){
    container.reserve(it2 - it1);
    while(it1 != it2)
      container.push_back(*it1++);
  }
};


template 
void get_sequence(T_Container & container)
{
  //...
  T_FillStrategy::fill(container, iter1, iter2);
  //...
}
于 2008-09-18T22:14:00.157 に答える
2

特別な注意を払うべきことの 1 つは、ライブラリが DLL などを意味するかどうかです。次に、アプリケーションなどのライブラリ コンシューマーが、ライブラリ自体とは別のコンパイラでビルドされている場合、問題が発生する可能性があります。

あなたの例でstd::vector<>by 値を返す場合を考えてみましょう。その後、メモリはライブラリのコンテキストで割り当てられますが、アプリケーションのコンテキストでは割り当てが解除されます。2 つの異なるコンパイラが異なる割り当て/割り当て解除を行う可能性があるため、大混乱が発生する可能性があります。

于 2008-09-18T22:24:11.863 に答える
1

シーケンスのメモリをすでに管理している場合は、呼び出し元がforループまたはアルゴリズム呼び出しで使用するイテレータのペアを返すことができます。

返されたシーケンスがそれ自体のメモリを管理する必要がある場合、事態はさらに複雑になります。@paercebalのソリューションを使用することも、shared_ptrを反復するシーケンスに保持する独自のイテレーターを実装することもできます。

于 2008-09-19T00:49:14.807 に答える
0

std::list<int>少しいいです、IMO。コピーされるのはポインターのみであるため、これにはリスト内のデータの追加のコピーは必要ないことに注意してください。

それは完全に消費者次第です。彼らが C++ 開発者であることが期待できる場合は、stdコンテナー クラスの 1 つを提供してください。

私に起こる唯一の他のことは、あなたがこれをするだろうということです:

void get_sequence(std::tr1::function<void(int)> f);

次に、呼び出し元は、関数を使用std::tr1::bindして、必要なオブジェクト (またはそうでないオブジェクト) の関数を呼び出すことができます。作成している各要素をget_sequence呼び出し続けるだけです。f

于 2008-09-18T22:10:20.307 に答える
0

次のようなことができます

template<typename container>
container get_sequence();

また、提供されたコンテナー タイプが標準インターフェイスに準拠している必要があります (インターフェイスのユーザーが vector/deque/list を使用できるように、push_back メンバーやおそらく reserve を持つなど)。

于 2008-09-18T22:13:03.420 に答える
0

iterator_traits を使用して、イテレータの型で静的にディスパッチできます

このようなもの :

template<T_insertIter> get_sequence(T_insertIter inserter)
{
   return get_sequence(inserter, typename iterator_traits<Iterator>::iterator_category());
}

template<T_insertIter> get_sequence(T_insertIter inserter, input_iterator_tag);
于 2008-09-18T22:19:30.377 に答える
0

シーケンスを出力するには、2 つのオプションがあります。最初は次のようなものです

template <typename OutputIter>
void generate_sequence(OutputIter out)
{
    //...
    while (...) { *out = ...; ++out; }
}

2番目は次のようなものです

struct sequence_generator
{
    bool has_next() { ... }
    your_type next() { mutate_state(); return next_value; }

private:
    // some state
};

標準アルゴリズム ( 、、 ...)で使用するには、標準 C++ イテレータ (boost::iterator_facade便宜上 を使用) に変換する必要があります。copytransform

boost::transform_iteratorまた、整数を順番に返すいくつかのイテレータと組み合わせて見てください。

于 2010-09-17T14:47:08.143 に答える
0

単一の値を受け入れるファンクターを関数に渡すことができます。ファンクターは、その時点で使用しているコンテナーに値を格納する責任があります。

struct vector_adder {
  vector_adder(std::vector<int>& v) : v(v) {}
  void operator()(int n) { v.push_back(n); }
  std::vector<int>& v;
};

void gen_sequence(boost::function< void(int) > f) {
  ...
  f(n);
  ...
}

main() {
  std::vector<int> vi;
  gen_sequence(vector_adder(vi));
}

注: ここでは、boost.function を使用して functor パラメーターを定義しています。これを行うためにブーストは必要ありません。それはそれをずっと簡単にします。

ファンクターの代わりに関数ポインターを使用することもできますが、お勧めしません。エラーが発生しやすく、データをバインドする簡単な方法はありません。

また、コンパイラが C++0x ラムダ関数をサポートしている場合は、明示的なファンクター定義を削除してコードを簡素化できます。

main() {
  std::vector<int> ui;
  gen_sequence([&](int n)->void{ui.push_back(n);});
}

(私はまだVS2008を使用しているので、ラムダ構文が正しいかどうかわかりません)

于 2010-09-17T15:01:47.967 に答える