2016년 8월 31일 수요일

Iterator에 대하여

다음과 같은 데이터의 묶음이 있다고 가정합시다.

class SomeClass
{
    ......
};
SomeClass SomeClassArray[1024]; // 1024개의 배열

이때 SomeClassArray의 모든 원소를 하나씩 꺼내기 위한 일반적인 방법은 다음과 같습니다.

    int k;

    for(k = 0; k < 1024; ++k)
        SomeClassArray[k].Read();

    for(k = 0; k < 1024; ++k)
        SomeClassArray[k].Process();

    for(k = 0; k < 1024; ++k)
        SomeClassArray[k].Write();

    for(k = 0; k < 1024; ++k)
        Func(&SomeClassArray[k]);

하지만 이 방법만 있는 것은 아니죠. SomeClassArray의 모든 원소를 하나씩 꺼내기 위한 다른 한가지 방법은 반복자(Iterator)를 사용하는 방법도 있습니다.

class SomeClassIterator // 반복자 정의
{
private :
    SomeClass *SomeClassArray;
    int        Size;
    int        Finger;
public :
    SomeClassIterator(SomeClass *arr, int size)
    {
        SomeClassArray = arr;
        Size = size;
        Finger = 0;
    }
    ~SomeClassIterator(void)
    {
    }
    SomeClass *operator ->(void)
    {
        return Finger >= Size ? nullptr : SomeClassArray[Finger];
    }
    SomeClass *operator *(void)
    {
        return Finger >= Size ? nullptr : SomeClassArray[Finger];
    }
    void operator ++(void)
    {
        ++Finger;
    }
    void Home(void)
    {
        Finger = 0;
    }
    bool IsEnd(void)
    {
        return Finger >= Size;
    }
};
    SomeClassIterator iter(SomeClassArray, 1024);  // 반복자 선언

    for(iter.Home(); !iter.IsEnd(); ++iter)
        iter->Read();

    for(iter.Home(); !iter.IsEnd(); ++iter)
        iter->Process();

    for(iter.Home(); !iter.IsEnd(); ++iter)
        iter->Write();

    for(iter.Home(); !iter.IsEnd(); ++iter)
        Func(*iter);

그런데 반복자는 왜 사용해야 할까요? 위에서 보듯 SomeClassIterator라는 클래스가 추가되는 등 오히려 더 복잡해지기만 하는데 말입니다.


만약 SomeClass와 SomeClassArray가 다음과 같이 바뀌면 어떻게 될까요?

class SomeClass
{
    ......
};
SomeClass2
{
    ....
    SomeClass Member;
};
SomeClass2 SomeClassArray[1024]; // 1024개의 배열

이 경우에 일반적인 for 루프를 사용했다면 다음과 같이 바꿔야 합니다.
    int k;

    for(k = 0; k < 1024; ++k)
        SomeClassArray[k].Member.Read();

    for(k = 0; k < 1024; ++k)
        SomeClassArray[k].Member.Process();

    for(k = 0; k < 1024; ++k)
        SomeClassArray[k].Member.Write();

    for(k = 0; k < 1024; ++k)
        Func(&SomeClassArray[k].Member);

더구나 저런 루프는 프로그램 전체에 흩어져 있을 것이므로 모든 for루프를 찾아 고치지 않으면 안되죠.

만약 iterator를 사용한다면
class SomeClassIterator // 반복자 정의
{
private :
    SomeClass2 *SomeClassArray;
    int         Size;
    int         Finger;
public :
    SomeClassIterator(SomeClass2 *arr, int size)
    {
        SomeClassArray = arr;
        Size = size;
        Finger = 0;
    }
    ~SomeClassIterator(void)
    {
    }
    SomeClass *operator ->(void)
    {
        return Finger >= Size ? nullptr : SomeClassArray[Finger].Member;
    }
    SomeClass *operator *(void)
    {
        return Finger >= Size ? nullptr : SomeClassArray[Finger].Member;
    }
    void operator ++(void)
    {
        ++Finger;
    }
    void Home(void)
    {
        Finger = 0;
    }
    bool IsEnd(void)
    {
        return Finger >= Size;
    }
};
    SomeClassIterator iter(SomeClassArray, 1024);  // 반복자 선언

    for(iter.Home(); !iter.IsEnd(); ++iter)
        iter->Read();

    for(iter.Home(); !iter.IsEnd(); ++iter)
        iter->Process();

    for(iter.Home(); !iter.IsEnd(); ++iter)
        iter->Write();

    for(iter.Home(); !iter.IsEnd(); ++iter)
        Func(*iter);

와 같이 SomeClassIterator클래스만 수정하면 프로그램의 다른 부분은 전혀 고칠 필요가 없습니다. 이것은 버그 가능성도 낮아지고, 유지보수비용이 감소함을 뜻합니다.

심지어, SomeClass와 SomeClassArray가 다음과 같이 리스트로 바뀌는 등 구조가 완전히 바뀌더라도

class SomeClass
{
    ......
    SomeClass *Next;
};
SomeClass *SomeClassArray; // 리스트구조의 첫항

만약 이렇게 바뀐다면, 일반적인 for루틴을 사용했을 경우는 다음과 같이 바뀌어야 합니다만,

    SomeClass *k;

    for(k = SomeClassArray; k != nullptr; k = k->Next)
        k->Read();

    for(k = SomeClassArray; k != nullptr; k = k->Next)
        k->Process();

    for(k = SomeClassArray; k != nullptr; k = k->Next)
        k->Write();

    for(k = SomeClassArray; k != nullptr; k = k->Next)
        Func(k);

만약 iterator를 사용한다면

class SomeClassIterator // 반복자 정의
{
private :
    SomeClass *SomeClassArray;
  //int        Size;
    SomeClass *Finger;
public :
    SomeClassIterator(SomeClass *arr, int size)
    {
        SomeClassArray = arr;
      //Size = size;
        Finger = arr;
    }

    ~SomeClassIterator(void)
    {
    }
    SomeClass *operator ->(void)
    {
        return Finger;
    }
    SomeClass *operator *(void)
    {
        return Finger;
    }
    void operator ++(void)
    {
        if(Finger != nullptr)
            Finger = Finger->Next;
    }
    void Home(void)
    {
        Finger = SomeClassArray;
    }
    bool IsEnd(void)
    {
        return Finger == nullptr;
    }
};
    SomeClassIterator iter(SomeClassArray, 1024);  // 반복자 선언

    for(iter.Home(); !iter.IsEnd(); ++iter)
        iter->Read();

    for(iter.Home(); !iter.IsEnd(); ++iter)
        iter->Process();

    for(iter.Home(); !iter.IsEnd(); ++iter)
        iter->Write();

    for(iter.Home(); !iter.IsEnd(); ++iter)
        Func(*iter);


와 같이 역시 Iterator 부분만 고치는 것으로 끝납니다.

즉 iterator는 반복루틴 자체를 추상화함으로써 유지보수비용을 줄이는 기법입니다.

댓글 없음:

댓글 쓰기