std::array (연속형)

Jin Hur·2021년 12월 28일
0

reference: "전문가를 위한 C++" / 마크 그레고리

C++ STL 표준 라이브러리, std::array

std::array는 메모리를 자동으로 할당하고 해제한다. std::array는 원소의 타입과 배열 크기를 매개변수로 사용하는 클래스 템플릿이다.

정적 배열과 유사한 동작을 하는 고정 크기 배열 클래스이며, 배열의 크기는 컴파일 시간에 결정된다. std::array는 배열의 데이터들은 힙이 아닌 스택 메모리에 할당된다.

std::array<int, 10> arr1;
arr1[0] = 1;

std::array<int, 4> arr2 = {1, 2, 3, 4};

std::array는 C 스타일 배열과 똑같은 방식으로 배열 원소에 접근할 수 있는 [] 연산자를 제공한다. [] 연산자에 접근하고자 하는 배열 원소 인덱스(index)를 지정할 경우, 빠른 동작을 위해 전달된 인덱스 값이 배열의 크기보다 작은지 검사하지는 않는다.

대신 std::array의 at(index) 형식의 함수도 함께 제공하며, 이 함수는 인자로 전달된 index 값이 유효하지 않으면 std::out_of_range 예외(exception)를 발생시킨다. ([] 연산자보다는 조금 느린 편)

std::array<int, 4> arr3 = {1, 2, 3, 4};

try{
    std::cout << arr3.at(3) << std::endl;	// no error
    std::cout << arr3.at(4) << std::endl;	// std::out_of_range 예외 발생
}
catch (const std::out_of_range &ex) {
    std::cerr << ex.what() << std::endl;
}

std::array 객체를 다른 함수에 전달하는 방식은 기본 데이터 타입을 전달하는 것과 유사하다. 값 또는 참조(reference)로 전달할 수 있고, const를 함께 사용할 수도 있다. C 스타일 배열을 함수에 전달할 때처럼 포인터 연산을 사용한다거나 참조(&) 또는 역참조(*) 연산을 하지 않아도 된다. 그러므로 다차원 배열을 전달하는 경우에도 std::array를 사용하는 것이 가독성이 훨씬 좋다.

void print(std::array<int, 5> arr){
    for(auto ele : arr)
        std::cout << ele << ', ';
}

std::arr<int, 5> arr = {1, 2, 3, 4, 5};
print(arr);

위 print() 함수는 매개변수 데이터 타입에 전달받을 배열 크기가 고정되어 있기에 다른 크기의 배열을 전달할 수 없다. 다양한 크기의 std::array 객체에 동작하는 범용적인 배열 출력함수를 만들고자 한다면, 함수를 템플릿으로 선언하고, 배열 크기를 템플릿 메개변수로 전달하면 된다.

template<size_t N>
void print(const std::array<int, N> &arr);

함수에 객체 전달시 값 복사

std::array 객체를 전달할 경우, 기본적으로 새로운 배열(별도의 주소)에 모든 원소가 복사된다. 즉, 자동으로 '깊은 복사'가 동작한다. 만약 이러한 동작을 피하고 싶다면 참조 또는 const 참조를 사용할 수 있다.

// 참조에 의한 호출로 복사에 대한 오버헤드를 줄인다.
void print(const std::array<int, 5>& arr){
    for(auto ele : arr)
        std::cout << ele << ', ';
}

std::arr<int, 5> arr = {1, 2, 3, 4, 5};
print(arr);

반복자(iterator)와 범위 기반 for문

배열의 원소를 차례대로 접근하는 연산에서 std::array는 (1) 반복자(iterator)(2) 범위 기반 for문을 이용하여 원소에 차례대로 접근할 수 있다.

// (2) 범위 기반 for문
for(auto ele : arr)
    std::cout << ele << ', ';

인덱스 값을 사용하는 for문의 경우 배열 크기를 정확하게 지정해야 한다. 만약 인덱스 값이 배열 크기보다 같거나 커지면 에러가 발생한다. **범위 기반 for 반복문(2)**을 사용하여 std::array의 모든 원소에 접근할 수 있는 것은 반복자를 사용하기 떄문이다. std::array는 begin()과 end()라는 이름의 멤버 함수를 제공하며, 이들 함수는 가장 첫 번쨰 원소와 가장 마지막 원소 다음 위치를 반환한다. 특정 원소 위치에서 다음 원소 위치로 이동하려면 반복자에 증가 연산자(++) 또는 덧셈 연산자(+) 같은 산술 연산을 수행할 수 있다.

범위 기반 for문은 begin() 위치부터 시작하여 증가 연산자를 통해 차례대로 원소를 이동하다가 end() 위치에 도달하면 종료된다.
(반복자는 array, vector, map, set, list처럼 반복 가능한 모든 STL 컨테이너에 대해 사용할 수 있다.)

iterator 활용 for문

// (1) 반복자를 활용한 for문
for(auto it = arr.begin(); it != arr.end(); it++){
    auto ele = (*it);
    std::cout << ele << ', ';
}

관계 연산자와 복사 할당 연산자

std::array는 깊은 비교를 위한 관계 연산자와 깊은 복사를 위한 복사 할당 연산자도 지원한다.
std::array에 저장되는 데이터 타입에서 크기 비교(관계 연산자: <, >, <=, >=, ==, !=)를 지원할 경우, 이들 관계 연산자를 이용하여 두 std::array 배열을 비교하는 용도로 사용할 수도 있다.

얕은 비교

C 스타일 배열에 대해서도 관계 연산자를 사용할 수 있지만, 배열 원소 값을 비교하는 것이 아닌 포인터 주소를 비교한다. 즉 깊은 비교가 아닌 얕은 비교이다.

std::array의 경우 값으로 비교할 수 있도록 함수가 재정의되어있다. 객체의 값이 동일한 지 판단하는 '==' 연산자가 재정의되어있는데, 배열의 길이와 그 요소들이 모두 같은 지 판단한다. 깊은 비교가 이루어진다.

얕은 복사

할당에 대해서도 C 스타일 배열은 메모리를 새로 생성하여 값을 복사(깊은 복사)하지 않으며, 단순히 같은 배열 데이터를 가리키는 새로운 포인터를 생성한다(얕은 복사).

cf) std::array에 대해 관계 연산자를 사용할 경우, 두 배열의 크기가 같아야 합니다. 이는 std::array로 생성한 배열 객체의 경우, 배열의 크기가 데이터 타입 일부로 동작하기 때문이다. 즉, 크기가 다른 배열은 서로 다른 타입으로 인식되므로 비교할 수 없다.


std::array 외 다른 컨테이너

std::array는 배열의 데이터들은 힙이 아닌 스택 메모리에 할당된다.

source: https://hackingcpp.com/cpp/std/sequence_containers.html

0개의 댓글