C++を勉強しているものです。
イテレータの利点が全く分かりません。
普通にポインタでいいのではないかと思っています。
検索してみてもメリットをわかりやすく説明しているところがなかったので
ここに質問しました。
イテレータはどのような場合で効果を発揮するのですか?
大規模開発の時でしょうか?
どなたかご教授お願いします。
C++ のイテレータの利点
Re: C++ のイテレータの利点
イテレータはポインタと違って要素のアドレスが連続している必要がありません。
Re: C++ のイテレータの利点
すみません。説明不足でした。
私が考えていたのは
このような場合です。
例えば
for(vector<int>::iterator i=vec.begin(); i!=vec.end(); ++i){
cout << vec << endl;
}
ではなく
for(unsigned int i=vec.begin(); i!=vec.end(); ++i){
cout << vec << endl;
}
を使えばいいんではと思ったのです。
イテレータをどういう場面で使えばいいのかわかりません。
私が考えていたのは
このような場合です。
例えば
for(vector<int>::iterator i=vec.begin(); i!=vec.end(); ++i){
cout << vec << endl;
}
ではなく
for(unsigned int i=vec.begin(); i!=vec.end(); ++i){
cout << vec << endl;
}
を使えばいいんではと思ったのです。
イテレータをどういう場面で使えばいいのかわかりません。
Re: C++ のイテレータの利点
そもそもそのコードは2つともイテレータの使い方が間違っているのでコンパイルできません。
Re: C++ のイテレータの利点
私見ですが、イテレータの利点はコンテナやアルゴリズムと合わせて考えれば良いと思います。
あるデータがコンテナに格納されている場合、イテレータはコンテナの種類を問わず、そこに格納されている
要素を「画一的に」走査(操作)する手段(手続き)を提供します。
アルゴリズムにおいてもイテレータを使用することで、
「画一的に」データ群を処理することができ、データの種類、データ群の管理方法に
依存しない処理を記述できるようになります。
自分でライブラリ関数を作成するときなど、1つのロジックでいろいろなデータに対応できるようになります。
こういったデータ操作の抽象化がイテレータの利点だと考えます。
あるデータがコンテナに格納されている場合、イテレータはコンテナの種類を問わず、そこに格納されている
要素を「画一的に」走査(操作)する手段(手続き)を提供します。
アルゴリズムにおいてもイテレータを使用することで、
「画一的に」データ群を処理することができ、データの種類、データ群の管理方法に
依存しない処理を記述できるようになります。
自分でライブラリ関数を作成するときなど、1つのロジックでいろいろなデータに対応できるようになります。
こういったデータ操作の抽象化がイテレータの利点だと考えます。
Re: C++ のイテレータの利点
イテレータがどういうときに便利なのかって、気になりますね:)
大きなプログラムを組んだことがないので、同じく、イテレータがすごく便利! と感じたことが無いです。
試しに、単なる配列をのクラス Array と、単純なリストのクラス List を作ってみました。
Array の中には配列が、List の中にはノードの根元が入っています。
いま、Array と List の中身を全て表示するプログラムを書かなければいけなくなったとします。
配列なら添え字でアクセスすればいいのがわかりますが、
ノードはそのノードに保存されている次のノードの場所を順々にたどらなくてはいけません。
単純に考えれば、配列とノードで、中身を表示するコードの書き方は大きくことなってくると思います。
そこでイテレータを実装してみます。
Aggregate: Array と List の基本クラス Iterator を返す関数 iterator() を持つ
Array: 配列クラス int配列 elements と、その配列に値を入れていく add() 関数を持つ
List: リストクラス ノードの根 m_Root と、値を持たせたノードを追加する add() 関数を持つ
ListNode: リストのノード 保持する値 data と、次のノードへのポインタ next を持つ
Iterator: IteratorBase へのポインタ
IteratorBase: 次の要素を返す関数 next() と次の要素があるかどうかを返す hasNext() 関数を持つ
ArrayIterator: Array のイテレータ
ListIterator: List のイテレータ
agg.h array.h
array.cpp
listnode.h
list.h
list.cpp
ite.h
ite_base.h
array_ite.h
array_ite.cpp
list_ite.h
list_ite.cpp
main.cpp
イテレータがあれば、 main.cpp で行っているように、配列のクラス Array に対しても、リストのクラス List に対しても、
同じコードで全ての中身の表示を行うことができました。
でもまだ便利な気がしません。
次に突然 Array や List の中身全てを、同じ数字で埋める関数が欲しくなったとします。
そんなとき、Agregate を次のように改造します。
agg.h
agg.cpp
イテレータに対する操作なら、Aggregate を継承するクラスがどんなふうでも、同じ動作を期待できます。
Array や List の実装を気にせずに Aggregate に fill() というイテレータを操作する関数を追加しただけで、
Aggregate を継承している Array にも List にも同じ数字で埋める関数を追加することができました。
fill() のコードを書くのに、Array や List のコードを見る必要は一切ありません。
便利なんでしょうか。
配列、リスト、スタック、キューの全てに、同じ動作をするようそれぞれに同名の関数を何種類も定義するのと比べれば、
それら全てにイテレータを定義して、イテレータに対し同じ操作を行う関数を何種類も定義する方が、
少し便利な気がします。
前者のコード量 = (コンテナ数 * 関数の数)分のコード
後者のコード量 = コンテナ数分のイテレータのコード + それぞれの関数のコード
でも、イテレータはすごく有名で、デザインパターンの中でもすごくいいものと言われています。
きっと大規模な開発を行っている人や、昔から業界に携わっている人は、イテレータで感動したことがあると思います。
どこかにそんな体験談はないものでしょうかX>
イテレータが便利な場合をもうちょっと考えて見ます。
1.配列やリストやその他のコンテナに、同様の操作を行う関数を作りたい場合
2.突然いままで書いてきたプログラムの中で使われている配列(もしくは他のコンテナ)を、
リスト(もしくは別の他のコンテナ)に替える場合
3.STLを作る場合
1は上のコードのことです。2はイテレータが配列もリストにも同様に操作できることを利用して、
コンテナの種類を替えるということです。そんなことをすることってあるんでしょうか……。
3.はトートロジー(って言うんでしたっけ)のような気がしますねX<
STLを作るのに便利なんじゃなくて、便利だからSTLに含まれてるんですよねたぶん。
大きなプログラムを組んだことがないので、同じく、イテレータがすごく便利! と感じたことが無いです。
試しに、単なる配列をのクラス Array と、単純なリストのクラス List を作ってみました。
Array の中には配列が、List の中にはノードの根元が入っています。
いま、Array と List の中身を全て表示するプログラムを書かなければいけなくなったとします。
配列なら添え字でアクセスすればいいのがわかりますが、
ノードはそのノードに保存されている次のノードの場所を順々にたどらなくてはいけません。
単純に考えれば、配列とノードで、中身を表示するコードの書き方は大きくことなってくると思います。
そこでイテレータを実装してみます。
Aggregate: Array と List の基本クラス Iterator を返す関数 iterator() を持つ
Array: 配列クラス int配列 elements と、その配列に値を入れていく add() 関数を持つ
List: リストクラス ノードの根 m_Root と、値を持たせたノードを追加する add() 関数を持つ
ListNode: リストのノード 保持する値 data と、次のノードへのポインタ next を持つ
Iterator: IteratorBase へのポインタ
IteratorBase: 次の要素を返す関数 next() と次の要素があるかどうかを返す hasNext() 関数を持つ
ArrayIterator: Array のイテレータ
ListIterator: List のイテレータ
agg.h array.h
#pragma once
#include "agg.h"
class ArrayIterator;
class Array : public Aggregate
{
friend ArrayIterator;
private:
int* elements;
int m_size;
int m_count;
public:
Array(int size);
~Array();
void add(int element);
Iterator iterator();
};
#include "array.h"
#include "array_ite.h"
#include "ite.h"
Array::Array(int size)
: m_size(size), m_count(0)
{
elements = new int[m_size];
}
Array::~Array()
{
delete[] elements;
}
void Array::add(int element)
{
if(m_count < m_size)
{
elements[m_count++] = element;
}
}
Iterator Array::iterator()
{
return Iterator(new ArrayIterator(this));
}
#pragma once
class ListNode
{
public:
ListNode* next;
int data;
ListNode(int srcData)
: next(nullptr), data(srcData)
{}
};
#pragma once
#include "agg.h"
class ListIterator;
class ListNode;
class List : public Aggregate
{
friend ListIterator;
private:
ListNode* m_Root;
public:
List();
~List();
void add(int data);
Iterator iterator();
};
#include "list.h"
#include "listnode.h"
#include "ite_base.h"
#include "list_ite.h"
#include <memory>
List::List()
: m_Root(nullptr)
{
}
List::~List()
{
if(m_Root == nullptr) return;
ListNode* Node = m_Root;
ListNode* NextNode;
while(Node != nullptr)
{
NextNode = Node->next;
delete Node;
Node = NextNode;
}
}
void List::add(int data)
{
ListNode* Node = new ListNode(data);
Node->next = m_Root;
m_Root = Node;
}
Iterator List::iterator()
{
return Iterator(new ListIterator(this));
}
ite.h
#pragma once
#include <memory>
#include "ite_base.h"
typedef std::shared_ptr<IteratorBase> Iterator;
#pragma once
class IteratorBase
{
public:
virtual int& next() = 0;
virtual bool hasNext() = 0;
};
#pragma once
#include "ite_base.h"
class Array;
class ArrayIterator : public IteratorBase
{
private:
Array* m_Array;
int m_index;
public:
ArrayIterator(Array* srcArray);
int& next();
bool hasNext();
};
#include "array_ite.h"
#include "array.h"
ArrayIterator::ArrayIterator(Array* srcArray)
: IteratorBase(), m_index(0)
{
m_Array = srcArray;
}
int& ArrayIterator::next()
{
return m_Array->elements[m_index++];
}
bool ArrayIterator::hasNext()
{
return (m_index < m_Array->m_size);
}
#pragma once
#include "ite_base.h"
class ListNode;
class List;
class ListIterator : public IteratorBase
{
private:
List* m_List;
ListNode* m_NextNode;
public:
ListIterator(List* srcList);
int& next();
bool hasNext();
};
#include "list_ite.h"
#include "list.h"
#include "listnode.h"
#include <iostream>
ListIterator::ListIterator(List* srcList)
{
m_List = srcList;
m_NextNode = m_List->m_Root;
}
int& ListIterator::next()
{
ListNode* ReturnNode = m_NextNode;
m_NextNode = m_NextNode->next;
return ReturnNode->data;
}
bool ListIterator::hasNext()
{
return (m_NextNode != nullptr);
}
#include "list.h"
#include "array.h"
#include <iostream>
int main()
{
Array DataArray(4);
List DataList;
DataArray.add(1);
DataArray.add(2);
DataArray.add(3);
DataArray.add(4);
DataList.add(5);
DataList.add(6);
DataList.add(7);
DataList.add(8);
std::cout << "array:" << std::endl;
// 配列の表示
Iterator arrIt = DataArray.iterator();
while(arrIt->hasNext())
{
std::cout << arrIt->next() << ',';
}
std::cout << std::endl << std::endl;
std::cout << "list:" << std::endl;
// リストの表示
Iterator listIt = DataList.iterator();
while(listIt->hasNext())
{
std::cout << listIt->next() << ',';
}
std::cout << std::endl << std::endl;
char dummy;
std::cin >> dummy;
return 0;
}
同じコードで全ての中身の表示を行うことができました。
でもまだ便利な気がしません。
次に突然 Array や List の中身全てを、同じ数字で埋める関数が欲しくなったとします。
そんなとき、Agregate を次のように改造します。
agg.h
#pragma once
#include "ite.h"
class Aggregate
{
protected:
virtual Iterator iterator() = 0;
public:
virtual void fill();
};
#include "agg.h"
void Aggregate::fill()
{
Iterator it = this->iterator();
while(it->hasNext())
{
it->next() = 32767;
}
}
Array や List の実装を気にせずに Aggregate に fill() というイテレータを操作する関数を追加しただけで、
Aggregate を継承している Array にも List にも同じ数字で埋める関数を追加することができました。
fill() のコードを書くのに、Array や List のコードを見る必要は一切ありません。
便利なんでしょうか。
配列、リスト、スタック、キューの全てに、同じ動作をするようそれぞれに同名の関数を何種類も定義するのと比べれば、
それら全てにイテレータを定義して、イテレータに対し同じ操作を行う関数を何種類も定義する方が、
少し便利な気がします。
前者のコード量 = (コンテナ数 * 関数の数)分のコード
後者のコード量 = コンテナ数分のイテレータのコード + それぞれの関数のコード
でも、イテレータはすごく有名で、デザインパターンの中でもすごくいいものと言われています。
きっと大規模な開発を行っている人や、昔から業界に携わっている人は、イテレータで感動したことがあると思います。
どこかにそんな体験談はないものでしょうかX>
イテレータが便利な場合をもうちょっと考えて見ます。
1.配列やリストやその他のコンテナに、同様の操作を行う関数を作りたい場合
2.突然いままで書いてきたプログラムの中で使われている配列(もしくは他のコンテナ)を、
リスト(もしくは別の他のコンテナ)に替える場合
3.STLを作る場合
1は上のコードのことです。2はイテレータが配列もリストにも同様に操作できることを利用して、
コンテナの種類を替えるということです。そんなことをすることってあるんでしょうか……。
3.はトートロジー(って言うんでしたっけ)のような気がしますねX<
STLを作るのに便利なんじゃなくて、便利だからSTLに含まれてるんですよねたぶん。