: 이전에 만들었던 가변 배열 클래스에 iterator 구현하기
반복자는 inner class이므로, 배열 클래스 내부에 클래스 만들기
T*가 8바이트고 int형 멤버변수가 2개 있으니까 이 클래스는 총 16바이트.
-> 이 때 내부 클래스에도 8바이트짜리 변수가 있다면?
-> 그럼 클래스 객체 하나는 24바이트?? ( X )
👉 CArr<T> 와 iterator<T> 는 별개의 클래스다. 반복자 선언이 가변배열 클래스 안에 되어있을 뿐임
클래스 템플릿 내부에 구현하는 것이므로 반복자도 템플릿이 된다.
vector<float>::iterator;
vector<short>::iterator;
vector<int>::iterator;
-> 따라서 이 셋은 각자 전혀 다른 클래스의 반복자임
반복자가 어떤 멤버를 가지고서 어떻게 동작해야 가변배열 안의 데이터를 가리키는 역할을 할 수 있을까?
-> 가변배열의 시작 주소, 인덱스 를 가지고
-> 시작 주소로부터 몇 칸 떨어져 있는지(인덱스) 파악한다.
생성자/소멸자 생성,
- 가변배열의 begin() 설명
vector<int>::iterator veciter = vecInt.begin();
: 반복자 객체 생성해서 가변배열의 시작주소 할당
-> 가변배열은 자신의 멤버함수begin()을 반복자에게 넘겨준다.
👉begin()의 반환 타입이 iterator라서 반복자 객체veciter가 받을 수 있음
iterator begin();라고 begin 함수를 선언하려는데, iterator 클래스가 더 밑에 있어서 iterator 타입을 아직 모름.class iterator; 라고 전방선언을 해준다.: 가변배열의 첫 번째 데이터를 가리킨다.
1)
template<typename T>
typename CArr<T>::iterator CArr<T>::begin()
-> begin()은 가변배열의 함수임
정의할 때 타입 앞에 typename 붙인 이유
: 반환 타입이 내부 클래스(iterator)일 경우. 이 함수가 특정 타입이라는 걸 알려주기 위해 이 키워드 붙여야 함.
-> (CArr<T>::iterator : 반환 타입)
2)
CArr<int>::iterator myiter = myvector.begin();myiter 가 반환값을 받는다.iterator iter;
iter.m_pData = this->m_pData;
iter.m_iIdx = 0;
return iter;-> iter.m_pData 는 iterator 쪽의 멤버 변수,this->m_pData 는 CArr<T> 쪽의 멤버 변수 //iterator(CArr* _pArr, T* _pData, int _iIdx)
iterator(T* _pData, int _iIdx)
// m_pArr(_pArr) (3)에서 추가됨
: m_pData(_pData)
m_iIdx(_iIdx)
{
}
// return iterator(this, m_pData, 0); (3)에서 수정됨
return iterator(m_pData, 0);
: 전위/후위 증감 연산자, *, ==, != 함수 구현하기

반복자가 가변배열의 첫 번째 데이터 주소를 알고 있을 때,
만약 가변배열에 데이터가 꽉 차서 재할당이 일어난다면, 반복자는 새 주소를 가리켜야 한다.
(비어버린 메모리 주소를 계속 가리키게 될 수도 있으므로)
-> 그러려면 반복자는 (배열 크기를 늘린 주체인) 가변배열 객체를 알아야 함
(재할당시 가변배열 객체가 새 메모리 주소, 즉 데이터 주소를 알고 있음)
반복자의 멤버 변수
private:
CArr* m_pArr; // iterator가 가리킬 데이터를 관리하는 가변배열
T* m_pData; // 데이터들의 시작 주소
int m_iIdx; // 가리키는 데이터의 인덱스
-> 데이터를 관리하는 가변배열 객체 m_pArr를 알아야 한다.
-> 반복자가 클래스 템플릿이므로, 가변배열의 데이터 저장 단위인 T 포인터 타입의 데이터 시작 주소 m_pData를 알아야 한다.
-> 데이터 시작 주소로부터 몇 칸 떨어져 있는지 알기 위해 인덱스 m_iIdx를 알아야 한다.
CArr* 라고 멤버변수 타입을 써뒀는데
둘이 똑같아서 굳이 안 적고 생략한건가?
내부클래스니까 상위클래스 타입네임은 안써도 되는거?
❗ 반복자가 단순히 데이터 시작 주소와 인덱스만 알고 있으면, 재할당 전/후 데이터 주소가 다른지 알 수 없음. 반복자는 생성될 때 초기화되니까 주소가 바뀌어도 알지 못하고, 계속 예전 주소로 접근하게 될 것임..
m_pData와 반복자의 m_pData가 동일한 주소를 가질 것임)CArr* 타입이 추가됐으므로 생성자를 수정해 준다.iterator(CArr* _pArr, T* _pData, int _iIdx)
: m_pArr(_pArr)
, m_pData(_pData)
, m_iIdx(_iIdx)
{
}
template<typename T>
typename CArr<T>::iterator CArr<T>::begin()
{
// 시작을 가리키는 iterator 를 만들어서 반환해줌
return iterator(this, m_pData, 0);
}
-> begin()이 CArr<T>의 멤버함수이므로 this를 넣어줘야 한다.
-> 이 때 this 는 가변배열의 주소를 의미한다.
- 내부 클래스의 특징
-> 클래스 안에 선언된 클래스를 지칭하려면, 외부 클래스부터 범위 지정 연산자로 접근해서 지칭해줘야 함.
-> 외부 클래스의 private 멤버에 접근할 수 있다.
template<typename T>
typename CArr<T>::iterator CArr<T>::begin()
{
// 시작을 가리키는 iterator 를 만들어서 반환해줌
if(0 == m_iCount)
return iterator(this, m_pData, -1); // 데이터가 없는 경우. begin() == end()
else
return iterator(this, m_pData, 0);
}
// CArr<T> 에 함수 선언
iterator end();
// 클래스 바깥에 함수 정의
template<typename T>
typename CArr<T>::iterator CArr<T>::end()
{
// 끝의 다음을 가리키는 iterator 를 만들어서 반환해줌
return iterator(this, m_pData, -1);
}
: 반복자가 현재 가리키는 주소의 값을 반환한다.
CArr<int>::iterator myiter = myvector.begin();
int ivalue = *myiter;
*myiter = 100; // Error
public:
T& operator * ()
{
// iterator가 알고 있는 주소와, 가변배열이 알고 있는 주소가 달라진 경우(공간 확장으로 주소가 달라진 경우)
// end iterator인 경우
if (m_pArr->m_pData != m_pData || -1 == m_iIdx)
{
assert(nullptr);
}
return m_pData[m_iIdx];
}
//예시
veciter = vecInt.begin();
--veciter; //Error
int k = 0;
++(++k);
👉 그러려면 (증가한 후의) 자기 자신을 반환해야 함.
연산이 끝난 후, 연산자를 호출시킨 객체가 다시 반환되어야 그 객체를 통해 연속해서 연산자 함수를 호출시킬 수 있게 된다.
예시뭐지;
전위 )
: 반복자가 현재 가리키는 대상의 다음 데이터를 가리키도록 한다.
👉 인덱스 1 증가
// ++ 전위
iterator& operator ++()
{
// 2. end iterator인 경우
// 3. iterator가 알고 있는 주소와, 가변배열이 알고 있는 주소가 달라진 경우 (공간 확장으로 주소가 달라진 경우)
if (m_pArr->m_pData != m_pData || -1 == m_iIdx)
{
assert(nullptr);
}
// 1. iterator가 마지막 데이터를 가리키고 있는 경우
// --> end iterator가 된다.
if (m_pArr->size() - 1 == m_iIdx)
{
m_iIdx = -1;
}
else
{
++m_iIdx;
}
return *this;
}
int 를 기반으로 컴파일러가 opterator ++ 가 후위 버전 ++이라고 인식하게 됨.// ++ 후위
iterator operator ++(int)
{
iterator copy_iter = *this;
// 후위를 호출시킨 객체는 다음 요소를 가리키게 됨
++(*this);
// 전위로 증가시키기 전 상태의 복사본을 반환
return copy_iter();
}
💡 팁
사용자가 직접 만들지 않아도 자동으로 생성된다.
// 예시
class CTest
{
private:
int m_i;
public:
CTest(const CTest& _other)
: m_i(_other.m_i)
{
}
};
-> 생성자 하나를 만들었으니 기본 생성자도 구현해 줘야 한다.
이렇게 두 번 나눠서 작업하지 말고
CTest t1,
t1.m_i = 100;
CTest t2;
t2 = t1;
객체를 생성함과 동시에 대입을 받게 되면 컴파일러가 그 동작을 복사 생성자로 바꿔준다.
복사 생성자 호출해서 객체 생성/대입을 동시에 해버리기
CTest t1,
t1.m_i = 100;
CTest t2(t1);
대입 연산자 호출하는 것처럼 보이지만, t3을 새롭게 생성하면서 t1을 대입받고 있다.
=> 그럼 복사 생성자임 ㅇㅇ
CTest t3 = t1;
-> 지금 객체의 생성과 대입을 동시에 하고 있는데?
=> 그냥 복사 생성사 호출해서 한 번에 처리해버리기.
--> 그럼 아까 구현한 후위 연산자 함수의 iterator copy_iter = this도 복사 생성자를 호출하는 것이다.
전위 )
: 반복자가 현재 가리키는 대상의 이전 데이터를 가리키도록 한다.
👉 인덱스 1 감소
예외처리 )
// -- 전위
iterator& operator --()
{
// 1. iterator가 첫 번째 데이터를 가리키고 있는 경우
// 2. iterator가 알고 있는 주소와, 가변배열이 알고 있는 주소가 달라진 경우
// 3. 데이터가 0개인 경우
if (m_pArr->m_pData != m_pData || 0 == m_iIdx || 0 == m_pArr->size() )
{
assert(nullptr);
}
// 4. end iterator인 경우
// --> 마지막 데이터를 가리키게 된다.
if ( -1 == m_iIdx )
{
m_iIdx = m_pArr->size() - 1;
}
else
{
--m_iIdx;
}
return *this;
}
앞서 만든 전위 연산자를 그대로 활용하여 구현한다.
- 반복자 지역변수를 만들어
후위 -- 연산자를 호출한 객체를 넣어준다.후위 -- 연산자를 호출한 객체(자기 자신)을 전위 연산자를 활용하여 감소시킨다.- 아까 만든 복사본(반복자 지역변수)을 반환한다.
-> 원본은 이미 감소되었고, 반환만 감소 전의 복사본으로 함
// -- 후위
iterator operator --(int)
{
iterator copy_iter = *this;
// 후위를 호출시킨 객체는 이전 요소를 가리키게 됨
--(*this);
// 전위로 감소시키기 전 상태의 복사본을 반환
return copy_iter();
}
참/거짓 결과를 반환하므로 bool 타입.
반복자를 인자로 받아 비교하는데, 이 때 직접 복사하지 않고 참조하여 비용을 절감한다. 또한 값을 수정하지 않고 비교만 하면 되기 때문에 const 참조로 받아온다.
this : 이 멤버함수를 호출시킨 객체_otheriter : this 와 비교될 객체 뭐가 같아야 같은 반복자라고 판단할 수 있을까?
: 가리키는 요소가 같을 때
-> 데이터 시작 주소(m_pData)와 데이터의 인덱스(m_iIdx)가 같을 때
== 연산자
bool operator == (const iterator& _otheriter)
{
if (m_pData == _otheriter.m_pData && m_iIdx == _otheriter.m_iItdx)
{
return true;
}
return false;
}
== 연산자를 호출하여 더욱 간단해졌다.bool operator == (const iterator& _otheriter)
{
return !(*this == _otheriter);
}
👉 자기 자신과 인자끼리 ==로 비교한 값을 반대로 반환한다.
[참고]
https://youtu.be/Z5p4UmjsZlY (2)
https://youtu.be/pv7cOxy_x7k (3)
https://youtu.be/s3UfCS4QAcI (4)
https://youtu.be/I8-wha1B0NE (5)