2013-05-08 6 views
13

私は、アイテムを線形配列で内部的に格納するカスタムベクターコンテナを持っています。昨夜、クラスのカスタムイテレータをSTLアルゴリズムで使用できるように実装しようとしていました。そうリニアストレージを持つコンテナのSTLアルゴリズムでイテレータの代わりにローポインタを使用できますか?

Live example with custom iterators

ながら、私は単にSTLアルゴリズムに生のポインタを渡すことができます発見し、彼らはうまく動作するようです:私はあなたがここで見ることができるいくつかの成功を収めています。ここでの例では、任意のイテレータなしだ:

#include <cstddef> 
#include <iostream> 
#include <iterator> 
#include <algorithm> 

template<typename T> 
class my_array{ 
    T* data_; 
    std::size_t size_; 

public: 

    my_array() 
     : data_(NULL), size_(0) 
    {} 
    my_array(std::size_t size) 
     : data_(new T[size]), size_(size) 
    {} 
    my_array(const my_array<T>& other){ 
     size_ = other.size_; 
     data_ = new T[size_]; 
     for (std::size_t i = 0; i<size_; i++) 
      data_[i] = other.data_[i]; 
    } 
    my_array(const T* first, const T* last){ 
     size_ = last - first; 
     data_ = new T[size_]; 

     for (std::size_t i = 0; i<size_; i++) 
      data_[i] = first[i]; 
    } 

    ~my_array(){ 
     delete [] data_; 
    } 
    const my_array<T>& operator=(const my_array<T>& other){ 
     size_ = other.size_; 
     data_ = new T[size_]; 
     for (std::size_t i = 0; i<size_; i++) 
      data_[i] = other.data_[i]; 
     return other; 
    } 
    const T& operator[](std::size_t idx) const {return data_[idx];} 
    T& operator[](std::size_t& idx) {return data_[idx];} 
    std::size_t size(){return size_;} 

    T* begin(){return data_;} 
    T* end(){return data_+size_;} 
}; 

template<typename T> 
void print(T t) { 
    std::cout << t << std::endl; 
} 

int main(){ 


    typedef float scalar_t; 
    scalar_t list [] = {1, 3, 5, 2, 4, 3, 5, 10, 10}; 
    my_array<scalar_t> a(list, list+sizeof(list)/sizeof(scalar_t)); 

    // works! 
    for (scalar_t* it = a.begin(), *end = a.end(); 
     it != end; ++it) 
     std::cout << ' ' << *it; 
    std::cout << std::endl; 

    // works! 
    std::for_each(a.begin(), a.end(), print<scalar_t>); 
    std::cout << std::endl; 

    // works! 
    my_array<int> b(a.size()); 
    std::copy(a.begin(), a.end(), b.begin()); 

    // works! 
    scalar_t* end = std::remove(a.begin(), a.end(), 5); 
    std::for_each(a.begin(), end, print<scalar_t>); 
    std::cout << std::endl; 

    // works! 
    std::random_shuffle(a.begin(), end); 
    std::for_each(a.begin(), end, print<scalar_t>); 
    std::cout << std::endl; 

    // works! 
    std::cout << "Counts of 3 in array = " << std::count(a.begin(), end, 3) << std::endl << std::endl; 

    // works! 
    std::sort(a.begin(), end); 
    std::for_each(a.begin(), end, print<scalar_t>); 
    std::cout << std::endl; 

    // works! 
    if (!std::binary_search(a.begin(), a.end(), 5)) 
     std::cout << "Removed!" << std::endl; 

    return 0; 
} 

Live example without iterators

ここでの私の質問は以下の通りです:

  1. これは常に線形ストレージを持っているコンテナのために働くのか?私はこれが例えばリンクリストのためにはうまくいかないことを知っています。
  2. もしこのような状況で動作するのであれば、なぜイテレータを実装するのが面倒なのですか?私はイテレータが私のコードとその他のものを一般化する方法を知っていますが、このシンプルな配列なら、私はポイントを見ません。
  3. このアプローチが常に有効な場合、私がやっていることの否定的な問題は何ですか?一つ目は、私はデータのカプセル化を壊しているのが分かります。

答えて

12

オペレータオーバーロードに基づくイテレータの機能の1つは、ポインタがすでにランダムアクセスイテレータであることです。 STLの初期段階では、既存のコードでアルゴリズムを使用するのが簡単になりました(プログラマにとってより親しみやすいインターフェイスになる)ように、これは大きな設計上の勝利でした。配列をラップするのはまったく合理的です。typedef T* iterator; typedef const T* const_iteratorを追加し、end()begin()&array[size]&array[0]を返し、コンテナをイテレータベースのアルゴリズムで使用してください。すでにわかっているように、これは要素がメモリ内で連続しているコンテナ(配列など)で動作します。

場合は、「本当の」イテレータを実装する場合があります:あなたは(このようなツリーやリストなど)さまざまな形の容器を持って

  • イテレータを無効にせずに配列のサイズを変更できます。
  • イテレータを無効にした後、またはコンテナが削除された後、またはbounds-checkに使用されているかどうかを確認するなど、イテレータ使用にデバッグチェックを追加する必要があります。
  • タイプセーフティを導入し、偶然にT*my_array::iteratorに割り当てることができないようにします。

私は、この最後の利点だけで、簡単なラッパークラスを書く価値はあると思います。異なる種類のものに異なる種類のものを作ってC++の型システムを利用しない場合は、Javascriptに切り替えることもできます:-)

+0

'&array [size]'には注意が必要です。http://stackoverflow.com/q/26420348/893406 –

+0

@ v.oddouいいえ、 '&array [size]'は常に有効です。 (あなたがそれを逆参照しない限り)、あなたのリンクの答えが説明するように。あなたは正しいコードであるかどうかを推論する必要があります:あなたは神秘主義を使うことはできません。 –

+0

ページIのリンク全体の議論によれば、このアドレスがマップされていなければ、クラッシュを引き起こすためにポインタレジスタにアドレスをロードすれば十分です。いくつかのアーキテクチャでは。したがって、それを逆参照しないことだけではありません。ここにはカーゴカルトはありません。 –

4

ポインタは、ランダムアクセスイテレータ(逆参照、増分、加算、差分など)に必要なインタフェースを提供し、イテレータと同様に扱うことができます。

  1. これは、連続したストレージを持つコンテナでは常に機能します。
  2. クラス内のすべてのパブリックデータではなく、メソッドを使用するのと同じ理由で、独自のイテレータを作成することができます。必要に応じて変更できるインターフェイスで発生していることをカプセル化します。 T*をイテレータの型にtypedefしている限り、これはおそらく重要な問題ではありません。さらに、単純なポインタ型に対してはできないイテレータ型でタグ付けされたイテレータからは、いくつかのアルゴリズムが役立ちます。
+2

ポインタは実際には完全に有効なイテレータであり、大部分だけでなく、*必要な*インタフェース全体を提供します。そのトリックは、ポインタ型( 'value_type'型定義の定義など)によって直接提供されない機能は、' std :: iterator_traits 'クラステンプレートの特殊化によって提供されるということです。ちょうどFYI :) – jalf

8
  1. はい。線形ストレージコンテナでは、アイテムのアドレスを単純に取り、単純な配列を指しているかのようにそのポインタで作業することができます。Effective STL, Item 16を参照してください。
  2. 私はあなた自身の質問–に答えたと思います。シンプルな配列があなたが必要とするすべてであることが分かっているなら、おそらくそうではありません。
  3. おそらく最大の問題は、ちょうどその–データカプセル化を破ることです。明示的なイテレータ・タイプのような抽象化がコストに対して何かを買うかどうかを検討してください。
+0

"単純な配列があなたが必要とするすべてであることを知っているなら" - >あまりにも多くの制約。 "あなたが今必要とするものはすべて"で十分です。神のためにYAGNIとKISSを尊重してください。そうでなければupvoted。 –

4

これは常に線形記憶域を持つコンテナでは機能しますか?

はい、イテレータの概念は、ポインタが配列に対してイテレータとして動作できるように設計されています。

このような状況で動作する場合は、とにかくイテレータを実装するという面倒を避ける必要がありますか?

単純なポインタではできない境界チェックのようなことをしたくない場合は、この状況で独自のイテレータタイプを定義するのは良い理由ではありません。

基本的なイテレータタイプの中には、イテレータの特性にネストされたtypedefを含めることができます。ポインタを使用すると、std::iterator_traits<T*>から入手できます。

このアプローチが常に有効な場合、私がしていることの否定的な問題は何ですか?一つ目は、私はデータのカプセル化を壊しているのが分かります。

STL形式のコンテナとのインタフェースは、より一貫性を持たせるに、あなたはiteratorconst_iteratorタイプ(ポインタのためのtypedefエイリアス)を定義し、beginendconstのオーバーロードを提供する必要があります。おそらくcbegincendはC++ 11互換です。

他にもさまざまな要件があります。詳細については、C++標準のセクション23.2を参照してください。しかし、STLスタイルのアルゴリズムはコンテナではなくイテレータで動作するため、イテレータを要件に準拠させることがより重要になります。

関連する問題