C++ のイテレータの利点

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
ジョン スミス

C++ のイテレータの利点

#1

投稿記事 by ジョン スミス » 9年前

C++を勉強しているものです。
イテレータの利点が全く分かりません。
普通にポインタでいいのではないかと思っています。
検索してみてもメリットをわかりやすく説明しているところがなかったので
ここに質問しました。
イテレータはどのような場合で効果を発揮するのですか?
大規模開発の時でしょうか?
どなたかご教授お願いします。

アバター
h2so5
副管理人
記事: 2212
登録日時: 13年前
住所: 東京
連絡を取る:

Re: C++ のイテレータの利点

#2

投稿記事 by h2so5 » 9年前

イテレータはポインタと違って要素のアドレスが連続している必要がありません。

ジョン スミス

Re: C++ のイテレータの利点

#3

投稿記事 by ジョン スミス » 9年前

すみません。説明不足でした。
私が考えていたのは
このような場合です。

例えば

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;
}

を使えばいいんではと思ったのです。

イテレータをどういう場面で使えばいいのかわかりません。

アバター
h2so5
副管理人
記事: 2212
登録日時: 13年前
住所: 東京
連絡を取る:

Re: C++ のイテレータの利点

#4

投稿記事 by h2so5 » 9年前

そもそもそのコードは2つともイテレータの使い方が間違っているのでコンパイルできません。

Poco
記事: 161
登録日時: 13年前

Re: C++ のイテレータの利点

#5

投稿記事 by Poco » 9年前

私見ですが、イテレータの利点はコンテナやアルゴリズムと合わせて考えれば良いと思います。
あるデータがコンテナに格納されている場合、イテレータはコンテナの種類を問わず、そこに格納されている
要素を「画一的に」走査(操作)する手段(手続き)を提供します。
アルゴリズムにおいてもイテレータを使用することで、
「画一的に」データ群を処理することができ、データの種類、データ群の管理方法に
依存しない処理を記述できるようになります。
自分でライブラリ関数を作成するときなど、1つのロジックでいろいろなデータに対応できるようになります。
こういったデータ操作の抽象化がイテレータの利点だと考えます。

林_林檎

Re: C++ のイテレータの利点

#6

投稿記事 by 林_林檎 » 9年前

イテレータがどういうときに便利なのかって、気になりますね:)
大きなプログラムを組んだことがないので、同じく、イテレータがすごく便利! と感じたことが無いです。

試しに、単なる配列をのクラス 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

コード:

#pragma once

#include "ite.h"

class Aggregate
{
protected:
    virtual Iterator iterator() = 0;
};
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();
    
};
array.cpp

コード:

#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));
}
listnode.h

コード:

#pragma once

class ListNode
{
public:
    ListNode* next;
    int data;

    ListNode(int srcData)
        : next(nullptr), data(srcData)
    {}
};
list.h

コード:

#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();

};
list.cpp

コード:

#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;
ite_base.h

コード:

#pragma once

class IteratorBase
{
public:
    virtual int& next() = 0;
    virtual bool hasNext() = 0;
};
array_ite.h

コード:

#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();

};
array_ite.cpp

コード:

#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);
}
list_ite.h

コード:

#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();

};
list_ite.cpp

コード:

#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);
}
main.cpp

コード:

#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;
}
イテレータがあれば、 main.cpp で行っているように、配列のクラス Array に対しても、リストのクラス List に対しても、
同じコードで全ての中身の表示を行うことができました。

でもまだ便利な気がしません。

次に突然 Array や List の中身全てを、同じ数字で埋める関数が欲しくなったとします。
そんなとき、Agregate を次のように改造します。

agg.h

コード:

#pragma once

#include "ite.h"

class Aggregate
{
protected:
	virtual Iterator iterator() = 0;

public:
	virtual void fill();
};
agg.cpp

コード:

#include "agg.h"

void Aggregate::fill()
{
	Iterator it = this->iterator();
		
	while(it->hasNext())
	{
		it->next() = 32767;
	}
}
イテレータに対する操作なら、Aggregate を継承するクラスがどんなふうでも、同じ動作を期待できます。
Array や List の実装を気にせずに Aggregate に fill() というイテレータを操作する関数を追加しただけで、
Aggregate を継承している Array にも List にも同じ数字で埋める関数を追加することができました。
fill() のコードを書くのに、Array や List のコードを見る必要は一切ありません。

便利なんでしょうか。
配列、リスト、スタック、キューの全てに、同じ動作をするようそれぞれに同名の関数を何種類も定義するのと比べれば、
それら全てにイテレータを定義して、イテレータに対し同じ操作を行う関数を何種類も定義する方が、
少し便利な気がします。

前者のコード量 = (コンテナ数 * 関数の数)分のコード
後者のコード量 = コンテナ数分のイテレータのコード + それぞれの関数のコード

でも、イテレータはすごく有名で、デザインパターンの中でもすごくいいものと言われています。
きっと大規模な開発を行っている人や、昔から業界に携わっている人は、イテレータで感動したことがあると思います。
どこかにそんな体験談はないものでしょうかX>

イテレータが便利な場合をもうちょっと考えて見ます。

1.配列やリストやその他のコンテナに、同様の操作を行う関数を作りたい場合

2.突然いままで書いてきたプログラムの中で使われている配列(もしくは他のコンテナ)を、
  リスト(もしくは別の他のコンテナ)に替える場合

3.STLを作る場合

1は上のコードのことです。2はイテレータが配列もリストにも同様に操作できることを利用して、
コンテナの種類を替えるということです。そんなことをすることってあるんでしょうか……。
3.はトートロジー(って言うんでしたっけ)のような気がしますねX<
STLを作るのに便利なんじゃなくて、便利だからSTLに含まれてるんですよねたぶん。

ジョン スミス

Re: C++ のイテレータの利点

#7

投稿記事 by ジョン スミス » 9年前

非常に丁寧な説明をありがとうございます。
おかげで 謎が解けました。
これからも精進します!!

閉鎖

“C言語何でも質問掲示板” へ戻る