본문 바로가기
C++/기초

C++ 기초 : erase (2)

글: 시플마 2024. 4. 24.

erase 함수를 구체적으로 작성하겠습니다.

 

아래 코드를 보시죠.

 

235번째 줄 if 문에 조건이 하나 더 

추가되었습니다. vector의 데이터 개수를 나타내는

멤버 m_iCount의 값보다 iterator의 인덱스 값이 크거나 같을 때도

오류 창이 출력됩니다. 만약 vector의 데이터 개수가 3개인데 

3번째 인덱스 또는 그 이상의 인덱스에 접근하면 오류로 인지하는 거죠.

 

 

242 ~ 247번째 줄은 vector의 데이터가 복사되는 과정입니다.

빈 공간이 생기면 뒤에 있는 데이터로 빈 공간을 채워 주는 거죠.

 

여기서 생각을 조금 해 보면,

만약 3개의 데이터가 있고 0번째 인덱스 공간을 제거했습니다.

그러면 1번째 인덱스의 값이 0번째 인덱스로 복사되고,

2번째 인덱스의 값이 1번째 인덱스로 복사되면서 

총 두 번의 복사가 일어나죠.

 

1번째 인덱스 공간을 제거하면

2번째 인덱스의 값이 1번째 인덱스로 복사되면서 

총 한 번의 복사가 일어나죠.

 

2번째 인덱스 공간을 제거하면

복사가 발생하지 않습니다.

 

수식으로 표현하자면 

데이터의 개수 - 제거된 인덱스 번호 - 1 = 복사가 일어나는 횟수

 

다시 표현하면

데이터의 개수 - (제거된 인덱스 번호 + 1) = 복사가 일어나는 횟수

로 나타낼 수 있습니다.

 

해당 수식을 코드로 표현한 것이 242번째 줄에 있는

iLoopCount입니다. vector가 갖고 있는 데이터의 개수에서,

iterator가 가리키는 인덱스 값 + 1만큼 빼고 이를 

iLoopCount에 대입하고 있죠.

 

이후 for 문을 통해 복사를 진행합니다.

 

가변 배열의 특정 인덱스에 다음 인덱스의 값을 덮어씌웁니다.

제거된 인덱스부터 시작해 i값을 하나씩 올려가며 앞 공간부터

뒤의 값을 복사해 나갑니다.

 

만약 반복해야 하는 횟수(iLoopCount)가 3이면

i값보다 iLoopCount의 값이 큰 경우까지만 반복하면 되겠죠?

 

근데 여기서 아래와 같은 상황을 생각해 봅시다.

 

처음에는 데이터가 네 개였습니다. (100, 200, 300, 400)

근데 0번째 인덱스를 제거하면서 뒤의 값이 앞으로 복사되었죠.

 

한 개의 데이터를 제거했는데 

데이터의 개수는 여전히 4개이며 

심지어 400이 두 개가 되었죠. 문제가 있을까요?

 

사실 그렇지는 않습니다. 

 

왜냐하면 복사가 완료되면

데이터의 개수를 나타내는 

m_iCount의 값이 하나 내려가게 되어

vector는 데이터가 3개 있다고 인지하죠.

그렇기 때문에 이후 데이터를 삽입하게 되면

마지막 400이 들어 있는 공간에 새로운 값이 들어가기

때문에 문제가 되지 않습니다.

 

하지만 아예 없다고 보기에는 애매한 게,

clear라는 함수가 있습니다. 들어 있는 데이터를

모두 제거하는 함수죠.

 

이를 vector에 직접 구현한다면 

아래 코드가 될 것입니다.

 

void clear() { m_iCount = 0; }

 

이를 vector의 멤버 함수로 추가하면 되죠.

 

데이터의 개수를 0으로 보고 기존에 어떤

데이터가 들어 있든 그냥 새로운 데이터가 들어오면

덮어씌우는 겁니다.

 

문제는 데이터의 개수를 0으로 본다고 해서

공간 자체가 줄어드는 건 아니라는 것이죠.

 

데이터를 100개 할당하고 나서 다 지운다고 해도

실제로는 100개의 공간이 할당되어 있는 것이고

여기에 데이터를 하나만 넣는다고 하면

99개의 공간이 낭비가 되겠죠.

 

이런 것을 방지하기 위해 vector는 다른 방법을 

쓰기도 하는데 지금 다루지는 않겠습니다.

 

 

253번째 줄에 iterator의 멤버 변수 bValid가

보입니다. 새로 추가한 변수입니다.

 

해당 iterator가 쓸 수 있는 것인지, 없는 것인지

나타내는 bool형 변수입니다.

 

표준 라이브러리에서 제공하는 vector는

제거한 공간을 가리키고 있는 iterator를

바로 사용할 수 없게 했었죠? 해당 iterator를

사용하려면 다시 자신에게 대입해야 했습니다.

 

23번째 줄의 주석을 제거한 후 실행하면 오류 창이 출력되죠.

 

직접 만든 erase 함수에도 이러한 경우를 재현하기 위해

iterator에 bValid 멤버를 추가한 겁니다. 

 

멤버가 추가되었으니

 

위 코드처럼 생성자도 수정을 해야 합니다.

 

기본 생성자의 경우 bValid의 값을 false로 합니다.

 

인자를 받아 생성되는 iterator는

가리키는 vector가 유효하고 idx가 0 이상,

즉 end iterator가 아닌 경우에 bValid의 값을

true로 해 줍니다.

 

또한 

 

iterator 클래스에서

* 연산자를 오버로딩한 곳도 수정해 줘야 합니다.

 

bValid 값 논리 부정 연산자(!)를 적용하여 

bValid의 값이 true인 경우 false로 바꾸어

오류 창이 뜨지 않게 하고 

bValid의 값이 false인 경우 true로 바꾸어

오류 창이 뜨도록 하는 겁니다.

 

다시 253번째 줄을 보시면

bValid의 값을 false로 바꾸는 것을 확인할 수 있습니다.

 

이는 erase 함수가 호출되면서 

복사가 모두 끝나면(공간이 제거되어)

인자로 받은 iterator는 제거된 공간을 가리켜

더 이상 사용할 수 없는 iterator로 

인지하게 하기 위함인 것이죠.

 

이처럼 인자로 받은 iterator의

멤버 변수 bValid의 값을 바꿔야 하기 때문에

매개 변수에서 const 키워드를 없앴습니다.

 

 

258번째 줄에서

인자로 받은 iterator의 값을

그대로 복사하여 반환합니다.

이것이 다시 iterator로 대입이 되겠죠.

 

 


 

 

잘 작동하는지 확인해 보겠습니다.

 

iterator가 가리키는 공간에 erase 함수를 적용하여 

제거하고 해당 iterator에 바로 역참조를 진행하였더니

오류가 발생하는 것을 확인할 수 있습니다.

 

이를 하나씩 디버깅하여 살펴 보니

 

아래 로컬 창에서 iterator의

멤버 bValid의 값이 false인 것이 보이시나요?

 

erase 함수가 호출되면서

iterator의 멤버 bValid의 값이 false가 된 것이죠.

 

erase 함수가 종료되면서 반환하는

iterator를 다시 기존 iterator에 대입하지 않으면

계속 bValid의 값이 false로 유지되며 

결국 * 연산자를 진행할 때

if 문에 걸려 중단이 발생하는 것입니다.

 

 

이제 erase 함수를 적용하고 나서

 

iterator에 대입한 후 역참조를 진행하니

오류가 발생하지 않고 변수 i에도 뒤에 있던

값이 잘 대입되는 것을 확인할 수 있습니다.

 

 

이번에는 erase 함수를 진행한 후

iterator를 통해 요소들을 순회하며 출력하겠습니다.

 

처음 공간을 erase 함수를 통해 제거하였습니다.

 

for 문을 통해 iterator가 가리키는 값을

출력한 후 iterator의 값을 올려 다음 공간의 값까지

차례대로 출력하였습니다.

 

처음 공간에 있는 10이 제거되어 출력되지 않았고

20과 30이 출력되는 것을 확인할 수 있습니다.

 

 

이번에는

 

처음 공간을 가리키는 iterator의 값을 하나 올려

다음 공간을 가리키게 한 후 erase 함수를 통해 

해당 공간을 제거하였습니다. 즉 두 번째 공간을

제거한 것이죠.

 

이후 for 문을 통해 iterator가 가리키는 값을 하나씩 

출력해 보니 20이 제거되어 10과 30이 출력되는 것을

확인할 수 있습니다.

 

 

 


 

 

 

1 ~ 10의 값이 순서대로 들어 있는 vector에

erase 함수를 적용하여 짝수만 남겨 보겠습니다.

 

25번째 줄에 있는 for 문을 통해

iterator가 가리키는 값을 2로 나누었을 때 나머지가 0이면,

즉 짝수이면 해당 공간을 erase 함수를 통해 제거합니다.

 

그리고 iterator의 값을 하나 올려 다음 공간을 가리키고

다시 if 문을 통해 검사하죠.

 

 

그렇게 짝수가 있는 공간을 제거하고 33번째 줄에 있는

for 문을 통해 vector의 값을 출력합니다. 

 

 

그러나 실행을 해 보면

 

오류 창이 뜹니다.

 

왜일까요?

 

문제는 25번째 줄의 for 문입니다.

정확히는 for 문 속 ++myiter 구문 때문이죠.

 

아래 그림은 if 문에 있는 erase 함수로 인해 

 

짝수인 10까지 제거된 상황을 나타낸 것입니다. 

 

현재 iterator는 vector의 마지막을 가리키고 있습니다.

즉 end iterator라는 것이죠.

근데 여기서 for 문의 ++myiter 구문 때문에

다음을 가리키게 됩니다. end인 공간을 넘어

가리키게 되어 오류 창이 출력되는 겁니다.

 

 

이를 해결하려면

 

for 문을 한 번 돌 때마다 iterator의 값을

올리는 게 아니라 erase 함수를 통해 제거가 

이루어지지 않았을 때만 iterator의 값을 올려

다음 공간에 접근해야 합니다.

 

이러면 10이 있는 공간이 제거되었을 때 

다음 공간을 가리키지 않고

다시 for 문으로 올라 가,

iterator와 vector의 end iterator가 

다른지 검사할 것입니다.

 

iterator가 가리키던 10이 사라지며

iterator는 end iterator가 되었기 때문에

공간을 더 넘어가지 않고 for 문을 안전하게 종료할 것입니다.

 

 

iterator가 가리키던 10이 사라지며

iterator가 end iterator가 되도록 하려면

아래 코드와 같이 생성자 부분을 수정해야 합니다.

 

140번째 줄을 보시면 if 문이 추가되었습니다.

 

erase 함수는 종료되면서 iterator를 임시 객체로 반환하죠?

이때 iterator의 생성자가 호출됩니다.

 

그렇기 때문에 생성자 부분에 if 문을 더 추가하여

생성자가 인자로 받은 vector의 데이터 개수와 

iterator의 인덱스 값이 같을 때,

end 함수를 호출하여 해당 iterator에 대입함으로써

해당 iterator가 end iterator가 되도록 합니다.

(만약 데이터의 개수가 3개이고,

iterator의 인덱스 값이 3이면

해당 iterator는 end iterator로 볼 수 있겠죠?)

 

 

main 함수의 29번째 줄처럼

erase 함수를 통해 반환되는 iterator를 

기존의 iterator에 대입하여 사용하는 것을 

이용했습니다.

 

erase 함수가 특정 조건에서 

end iterator를 반환하도록 구현하여 

기존의 iterator에 end iterator를 대입할 수 있도록

유도한 것이죠.

 

 

 


 

 

 

이번에는 1 ~ 10까지의 숫자 중

1 ~ 5를 제거하고 남은 숫자를 출력해 보겠습니다.

 

27번째 줄의 if 문을 보시면

iterator가 가리키는 공간의 값이 1보다 크거나 같고,

동시에 5보다 작거나 같으면 erase 함수를 통해

제거하도록 되어 있습니다.

 

 

실행 결과 1 ~ 5가 제거되어 6 ~ 10이

출력되는 것을 확인할 수 있습니다.

 

 

 

 

 

강의 출처 : https://www.youtube.com/watch?v=PFc4g8mxOiI&list=PL4SIC1d_ab-aOxWPucn31NHkQvNPHK1D1&pp=iAQB


 

'C++ > 기초' 카테고리의 다른 글

C++ 기초 : inline  (2) 2024.04.26
C++ 기초 : list iterator  (0) 2024.04.25
C++ 기초 : erase (1)  (0) 2024.04.24
C++ 기초 : iterator (5)  (0) 2024.04.22
C++ 기초 : iterator (4)  (0) 2024.04.22