2016년 4월 3일 일요일

Error : iterator not incrementable, decrementable, dereferencable, incompatible



 그래픽스 관련 코딩을 하면서 STL을 사용하다가 위와 같은 에러가 발생 하였는데, 이유를 찾는데 꽤나 시간이 소요 되었다. STL을 다루는데 서투른 사람은 나와 같은 실수를 저지를거 같아서 글로 남기게 되었다.

 STL의 Sequence Container 사용하면서 다음과 같은 runtime error 문구를 발견 한 적이 있을 것이다. 이 같은 에러는 컴파일 에러가 아니기 때문에 확인할 수가 없어서, 직접 디버깅을 하여 찾아봐야 한다.

 보통 해당 컨테이너의 범위를 넘어가면 런타임 에러로 out of range를 표시해준다. 이런 에러는 쉽게 원인을 찾을 수 있다. 저 에러도 어찌보면 비슷한 경우에 발생하지만, 처음 저 에러를 겪는 사람들은 혼란스러울 것이다. 디버깅을 하지 않고, 출력으로 찾는다면 많은 시간이 소요 될 것이다. 이 글을 쓰는 나도 디버깅을 통해 저 에러의 원인을 발견하였다.

 이 에러가 발생하는 대부분의 이유가 다음과 같다고 생각한다.

for (iter = AEL.begin(); iter != AEL.end(); iter++;) {
}

 STL을 사용하는 사람이라면, 이와 같은 반복문 형식을 자주 사용할 것이다.

 컨테이너의 원소를 삭제하려고 한다면, 어떻게 할 것인가? 만약 삭제후에도 반복문을 계속 돌아야 한다면?

for (iter = AEL.begin(); iter != AEL.end(); iter++;) {
if (Condition) {
AEL.erase(iter);
}
}

 이렇게 구현하는 사람이 많을 것이다. 하지만 빌드해보면 에러가 발생할 것이다. 그렇다면 이유는 무엇일까? 단순하게 코드로만 보면 맞는거 같지만, STL iterator의 원리와 컨테이너의 멤버함수 원리만 알면 쉽게 알 수 있다.

 erase(iter) 멤버함수는 iter라는 반복자가 가리키는 원소를 제거한다. 원소를 제거 후 바로 다음 원소를 가리키는 반복자를 반환한다.

 이렇게 말해도 에러의 원인을 눈치채지 못하는 사람이 많을 것이다.


 다음 그림과 같이 해당 반복자 부문을 erase 멤버함수를 통해 제거하면 iter 반복자가 무엇을 가리키고 있는지 알 수 없다. 해당 원소가 제거 됬기 때문이다.
"iter++가 있잔아요? 그럼 iter이 다음 원소를 가리키는게 아닌가요?" 라고 말할 사람들이 있을 것이다.

 예를 들면 iter 반복자가 컨테이너 원소 값 주소 0x0001 번지를 저장하고 있다고 하자.
해당 반복자가 erase되면 컨테이너 원소 0x0001번지는 소멸된다. 그럼 반복자는 쓰레기 값을 가리키고 있는것이다. 반복자가 가리키는 값을 기준으로 증가하거나 감소할 수 있는데 해당 반복자는 무엇을 가리키는지 알 수 없기 때문에, 다음과 같은 에러가 발생할 것이다.
(Error : iterator not incrementable, decrementable, dereferencable, incompatible)

 "포인터처럼 다음 주소를 가리키면 되는게 아닌가" 라고 생각할 수도 있다. 이건 지극히 나의 주관적인 생각이지만,  STL 자체적으로 안전성 때문이라고 생각한다. 반복자는 포인터를 흉내낸 형태이지만, STL의 여러 컨테이너와 호환을 위해서 iterator 내부적으로 막아논 것으로 예상된다. 이 부분에 대해서는 더 공부 후 알게 되면 글을 수정하도록 하겠다.

 그렇다면 해결책은 무엇일까? erase가 반환하는 다음 원소를 iter 반복자가 다시 저장하는 방법으로 이용해야한다. 또한 반복문안에 iter++을 제거후 논리적으로 표현해야한다. 왜냐하면 erase가 반환후 다음 원소를 가리키는데 거기에 또 iter++를 하게되면 반복자가 두 번 이동하기 때문이다.

for (iter = AEL.begin(); iter != AEL.end();) {
if (Condition) {
iter = AEL.erase(iter);
}
else {
iter++;
}
}

 이와 같이 반복문의 증감부분을 없애고, 반복문 안에 조건문을 통해 erase가 반환한 반복자를 사용하면 된다.

댓글 없음:

댓글 쓰기