std::vector의 요소를 참조하는 방법은 두 가지가 있다. 바로, at()과 []을 이용하는 방법이다.
예를 들면 아래와 같다. 참고로 std::vector로 v를 만들었다고 가정해보자.
- v.at(1)
- v[1]
위처럼, std::vector의 요소를 참조할 때, 두 가지 방법을 사용할 수 있다. 그럼, 왜 같은 동작을 두 가지로 나누었을까? 그냥 사용자가 사용하기 편하라고 만들 것일까..
내 생각에는 안정성의 차이라고 할 수 있다. 배열을 참조할 때, 가장 큰 문제라고 한다면, 만들어지지 않은 주소 또는 요소를 참조하는 경우라고 할 수 있다. 이럴 경우에는 잘못된 메모리를 참조하여 문제가 발생하면 다행이지만, 운이 좋아(?) 문제가 발생하지 않는다면 이상한 공간을 참조하게 되는 것잇다.
그래서 std::vector를 만들 때, 벡터의 사이즈를 아는 경우와 아닐 경우에 따라서 함수를 사용하라고 만들어 준 것이 아닐까 싶은 생각이 든다.
# at()과 []의 동작 차이
위에서 말한 것처럼, 결국에는 요소를 참조할 때, 안전하게 참조하냐 아니냐의 차이이다. 그러므로, 함수가 어떻게 구현 되었는지를 확인하는게 가장 빠르게 이해할 수 있지 않나 싶다.
내 환경에서는 아래 파일에서 확인할 수 있었다. 보통 c++의 버전만 다를 뿐, 위치는 비슷 하지 않을까 싶다.
/usr/include/newlib/c++/9.2.1/bits/stl_vector.h
* 먼저, at() 함수는 아래처럼 정의 되어 있다.
1
2
3
4
5 | at(size_type __n)
{
_M_range_check(__n);
return (*this)[__n];
}
|
위에 함수는 굉장히 간단하다. 즉, at의 참조하고자 하는 위치로 _n을 받는다. 이후, _M_range_check() 함수로 참조할 수 있는지 확인한 후에 _n 위치에 있는 값을 반환하는 것이다.
따라서, at() 함수를 사용하다가 잘못된 위치를 참조하게 되면, 아래와 같은 예외 처리가 발생한다.
terminate called after throwing an instance of 'std::out_of_range'
what(): vector::_M_range_check: __n (which is 2) >= this->size() (which is 1)
Aborted (core dumped)
이처럼, at() 함수를 사용하면, vector의 사이즈를 넘어서는 요소를 참조하였다는 것을 빠르게 확인할 수 있다.
* 다음으로 [] 함수의 정의를 살펴 보자.
1
2
3
4
5 | operator[](size_type __n) _GLIBCXX_NOEXCEPT
{
__glibcxx_requires_subscript(__n);
return this->_M_impl._M_start[difference_type(__n)];
}
|
일단, 3번 줄을 보면 __glibcxx_requires_subscript(__n) 라는 것이 있는데, 따라가보면 assertions.h 파일에 정의가 되어 있긴 하다. 대충 아래처럼 정의 되어 있다고 보면 될 것이다.
1
2
3
4
5
6
7
8 | #ifndef _GLIBCXX_ASSERTIONS
# define __glibcxx_requires_subscript(_N)
...
#else
# define __glibcxx_requires_subscript(_N) \
__glibcxx_assert(__builtin_expect(_N < this->size(), true))
...
#endif
|
아무래도 이 파일이 심볼로 만들어지지 않아서 디버깅으로 해당 부분을 확인할 수 없었다. 따라서 추측성으로는 2번 라인을 타는 것이 아닐까 싶다.
즉, operator[] 정의 부분을 보면, 3번은 아무 의미가 없다는 뜻이고, 4번 라인에서 요소의 값을 반환한다는 것이다.
그 결과, 프로그램을 돌리는 환경에 따라 다르겠지만, 내 환셩에서는 0이라는 값이 나왔다. 값이 있지 않는 요소를 반환한 결과라고 보면 될 것 같다. (환경에 따라서는 에러가 발생하기도 한다.)
# 정리하면..
위에 정의된 부분을 보면 알겠지만, 둘의 차이는 안전한 요소를 참조하느냐가 끝이다.
하지만 반복문을 이용하여 vector의 요소를 확인한다면 속도의 차이가 발생한다. 왜냐하면, at()의 경우에는 반복해서 사이즈를 확인할 것이기 때문이다. 물론, 이게 얼마나 프로그램에 영향이 가겠느냐만은.. 혹시나 소형 임베디드 시스템이라면 문제가 발생할 수도 있다고 생각한다.
그러므로, 반복문을 사용할 때는 미리 사이즈를 확인하고, operator[]를 이용하면 안전하게 vector를 이용할 수 있을 것으로 생각한다.
댓글
댓글 쓰기