Game Develop

[C++] erase()와 iterator 본문

C++/C++

[C++] erase()와 iterator

MaxLevel 2022. 11. 15. 05:11

1. STL의 end()멤버함수는 마지막원소 다음의 주소를 가리키고있다. 

    해당 주소를 역참조하면 쓰레기값이 들어있으며, 애초에 역참조하려 하면 에러를 띄운다.

    마지막 원소를 얻고싶으면 아래와 같이한다.

 

vector<int> v = {0,1,2,3,4,5};      //   예시 컨테이너는 이걸로 계속 사용.

int lastElement = *(--v.end());     //    end()는 마지막의 다음 주소이기때문에 --연산.

 

 

2. erase(iter)를 할 경우, iter를 포함해서 그 이후의 iterator들의 값들은 다 유효하지 않다. (매우중요)

    때문에 역참조를 할 경우 에러가 발생하며, iter이전의 값들에 대해서만 역참조가 가능하다. (굳이 하려한다면)

    erase()의 리턴값은 iterator이며, iter을 삭제한 후에 유효한 iterator(다음 원소)를 리턴하기때문에, 계속해서 탐색하려면 

    iter에 이 리턴값을 받아야한다.

  

    iter = v.erase(iter);  <-- 이런식으로.

 

   

아래의 예시코드를 보자.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
vector<int> v = { 0,1,2,3,4,5 };
vector<int> ::iterator iter = v.begin();
vector<int> ::iterator end = v.end();
 
while (iter != end) // 여기서 에러뜸.
{
    if (*iter == 3)
    {
        v.erase(iter++);
    }
    else
    {
        iter++;
    }
}
cs

 

위 코드를 에러가 안나게 수정해보자. 

위 코드를 실행하면, erase를 수행한 이후 다시 if조건문을 왔을때 해당부분에서 에러가 뜬다.

(mycont가 nullptr이라고 뭐라 뜨는데, 유효하지않은 값이라고 생각하자.)

 

erase를 수행하면 그 이후의 iterator값들은 무효화가 되니 다시 잡아줘야한다고 했다.

일단 v.erase(iter++)을 수행한다는게 무슨 의미인지 알아야한다.

증감연산자를 전위로 하는것과 후위로하는것은 아예 다르다. (모른다면 관련되서 꼭 공부해야한다.)

저렇게 함수매개변수로 넘긴것을 예시로 설명하자면, 후위연산자로 할 경우 증감연산을 '하기 전'값을 넘기고,

이후에 '자기자신한테 증감연산을 적용한 값을 대입'한다. 

여기서 진짜 더 중요한게 있는데, '자기자신한테 증감연산을 적용한 값을 대입' 을 수행할 때의 자기자신은,

함수를 수행하기 전 원본을 말한다.

 

따로 강조하는 이유는, 아래의 두 코드가 똑같다고 생각할 수 있기 때문이다.

 

1.   v.erase(iter++);

2.   v.erase(iter);

      iter = iter+1

 

1번과 2번이 동일할까? 그러면 위의 예시코드에서 1번코드대신 2번코드를 넣으면 1번코드처럼 일단 해당 if문은 무사히 통과할까?  물론 그렇지않다.

1번은 일단 해당 if문(*iter == 3)을 통과후 다시 다음 if조건문(*iter == 3)에서 에러가 뜨지만, 2번은 iter = iter+1에서 바로 에러가 뜬다. 

왜냐하면 erase(iter)을 수행함으로써 이미 iter는 이미 유효하지않은 iterator이다.

여기에다가 +1이든 -1이든 뭔 연산을 하든 유효하지않은 값에다가 하는 행위이기에 에러가 뜬다. (마찬가지로 mycont = nullptr이라고 발생할것이다)

 

1번코드가 일단 해당 if문이라도 통과하는 이유는 후위연산이기 때문이다.

후위연산은 일단 임시공간에 원본값을 복사 후, 메모리에 접근해서 실제로 +1을 수행한다. 그다음 임시공간의 원본값을 리턴하게된다.

이걸 v.erase(iter++)에 그대로 대입하는 상황을 생각해보자.

일단 유효한 상태의 iter를 임시공간에 복사 후, 메모리에 접근해서 실제로 iter에 +1을 수행한다.

여기까지 된다. 왜? iter는 아직 유효하니까. erase가 수행된 이후에야 iter는 유효하지않게된다.

그다음에서야 임시공간의 원본값을 erase에 매개변수로 넘기고, erase가 수행되어서 그제서야 iter는 유효하지 않게된다.

iter는 유효하지 않기때문에, 이전에 iter+1한값 역시 유효하지 않게되고 다음 if문때야 비로소 에러가 발생한다.

 

 

어쨌든 그렇기떄문에 1번과 2번코드는 다른코드다...

그러면 올바르게 수정하기 위해서는 v.erase(iter++)을   iter = v.erase(iter)로 수정하면 되지않을까? 라는 생각이 들 것이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
vector<int> v = { 0,1,2,3,4,5 };
    vector<int> ::iterator iter = v.begin();
    vector<int> ::iterator end = v.end();
 
    while (iter != end)
    {
        if (*iter == 3)
        {
            iter = v.erase(iter);
        }
        else
        {
            iter++;
        }
    }
cs

 

이건 iter에 유효한 iterator를 넣어줬으니 되지않을까? 하겠지만... 이것도 에러가 난다.

이건 if문에 문제가있는건 아니고, while조건문의 end가 문제다.

end가 뭔지 보면... 맨 초기의 vector의 end()함수의 리턴값이다.

근데 반복문 중간에 if문에서 erase함수를 한번 수행했기 때문에 이 end값도 유효하지 않은값이 되버렸기에 문제가 된다.

저 end부분만 v.end()로 수정해주면 이제 에러가 발생하지 않는다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
    while (iter != v.end())
   {
        if (*iter == 3)
        {
            iter = v.erase(iter);
        }
        else
        {
            iter++;
        }
    }
cs

 

드디어 에러는 나지않는 코드가 완성된다.

근데 사실 iter로 erase의 리턴을 안받더라도, 이전처럼 증감연산자를 이용해서 똑같은 결과를 구현하는게 가능하다.

총 경우의 수는 4가지이다.

 

증감 전위 ++,--

증감 후위 ++,--

 

여기서 되는 경우의 수는 딱 한가지다. 후위 --를 할때이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 
    while (iter != v.end())
    {
        auto temp = v.end();
 
        if (*iter == 3)
        {
            //iter = v.erase(iter);
            v.erase(iter--);
        }
        else
        {
            iter++;
        }
    }
cs

왜 후위--는 되고, 나머지 3개는 안될까?

일단 전위 ++,-- 는 당연히안된다. v.erase(++iter)를 하든, v.erase(--iter)를 하든 erase를 수행후에 iter에는 유효하지않은 iterator가 들어가버리기 때문에 안된다. erase수행후에 iter에 유효한 iterator를 넣어주는 코드가 없으니 안되는게 맞다.

 

그러면 iter++은 왜 안될까...는 위에 설명했었다.

 

여기쯤 설명하면 아마 erase(iter--)은 왜 되는지 알 것이다.

임시공간에 iter복사 후, 메모리에 접근해서 --를 수행한다. 그리고 erase에는 임시공간의 iter원본'값'을 넘기게 된다.

erase를 수행하면 '매개변수로넘긴 iter'를 포함한 iter이후의 것들이 유효하지 않은 것이다.

현재 iter에는 이미 --가 수행되어서 안전한영역의 iter이기 때문에(erase가 수행된다고 앞의 값들의 위치가 바뀌진 않으니까) 정상적으로 반복문이 수행되는 것이다.

 

그렇기 때문에 위의 코드가 가능하다... 라는 결론이 나온다.

 

이런저런 실험하면서 헷갈려서 시간이 좀 걸렸는데, 일단 erase(iter++)을 수행한 이후에는 iter는 유효하지않은 값이라 했었다.

그래서 erase(iter++) 수행 후에 iter변수에 커서를 갖다대면 vector의 원소값이 표시가 되는것이다.

유효하지않은 iterator라서 뭔가 이상한값이 표시될줄알았는데, vector에 들어있는 원소값이 표시가 되니 '이게 뭐지?' 했었다. 

확인결과, STL의 각 컨테이너들은 _Ptr이라는 실제로 노드를 가리키는 주소값이 있다.

iter가 유효하지않더라도 이 iter의 _Ptr을 역참조하면 해당 주소의 원소값이 출력되긴한다.

즉, 정말 내부적으로는 완전 컴퓨터의 엉뚱한 메모리주소를 가리키는건 아니라는 것이다. 

그냥 이런게 있다고만 알아두자. 어차피 우리가 다룰건 iterator니까.

 

이외에 이것저것 잡다한거 쓰자면...

iterator도 []연산을 지원한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
    vector<int> v = { 0,1,2,3,4,5 };
    vector<int> ::iterator iter = v.begin();
    vector<int> ::iterator end = v.end();
 
    for (int i = 0; i < v.size(); i++)
    {
        cout << "&vector[i]값 : " << &v[i] << endl;
        cout << "vector[i]값 : " << v[i] << endl;
        cout << "&iter[i]값   : " << &iter[i] << endl;
        cout << "iter[i]값    : " << iter[i] << endl;
        cout << endl;
    }
cs

 

위 코드처럼 iter를 컨테이너의 begin을 대입해놨다면, vector일 경우 위처럼 그냥 v[i]하는것처럼 사용이 가능하다.

저기서 &v[i]랑 &iter[i]는 똑같은 주소값을 출력하고, v[i]랑 iter[i]도 똑같은 원소를 출력한다.

다만, iter가 begin()이 저장되어있기 때문이고 iter에 begin()이 아니라 begin()+1을 대입해놨다면 v[i]보다 +1의 인덱스의 주소와 원소를 출력할 것이다. 그리고 +1된 인덱스의 주소와 원소값들을 출력하다보니 마지막에는 v.end()에 해당하는부분을 참조하게되어서 결국 에러가 뜰 것이다. 

즉, iter는 자기자신을 기준으로 0,1,2,3.. 진행하는것이다.

v[0]은 맨 처음원소인 0을 가리키는게 맞지만, iter에 begin()+1을 대입해놓는다면 iter[0]은 0이 아니라 1이다.

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

[C++] 문자열리터럴  (0) 2022.11.19
[C++] memcpy 사용해서 char배열 복사  (0) 2022.11.19
[C++] Iterator (반복자)  (0) 2022.11.02
[C++] vector  (0) 2022.11.01
[C++] map에서 원소 넣을때 insert도 활용하자.  (0) 2022.10.29