모두의 코드
C++ 레퍼런스 - std::array (안전한 배열)

작성일 : 2020-07-17 이 글은 294 번 읽혔습니다.

아직 C++ 에 친숙하지 않다면 씹어먹는 C++ 은 어때요?

std::array

<array> 에 정의 되어 있음 (C++ 11 에 추가됨)

template <class T, std::size_t N>
struct array;

std::array 는 고정된 크기의 배열을 담고 있는 컨테이너 이다.

이 컨테이너는 마치 C 언어에서의 배열인 T[N] 과 비슷하게 작동하는데, 예를 들어서 C 배열 처럼 {} 를 통해 초기화 할 수 있습니다. (예컨대 std::array<int, 3> a = {1,2,3}). 다만 한 가지 차이점은 C 배열과는 다르게 배열의 이름이 T* 로 자동 형변환 되지 않습니다.

std::array 를 통해서 기존의 C 배열과 같은 형태를 유지하면서 (오버헤드가 없습니다), C++ 에서 추가된 반복자라던지, 대입 연산자 등을 사용할 수 있습니다. (즉 <algorithm> 에 정의된 함수들을 std::array 에도 사용할 수 있다는 의미 입니다.)

참고로 크기가 0 인 std::array 의 경우 (즉 N 이 1 일 때), array.begin() == array.end() 이며, front()back() 을 호출할 시 그 결과는 정의되지 않습니다 (Undefined behavior).

쉽게 생각해서 std::arrayN 개의 같은 타입의 원소들을 담고 있는 tuple 이라 보시면 됩니다.

반복자 무효화

std::array 의 반복자는 배열 객체가 살아있는 동안 절대로 무효화 되지 않습니다. 다만 swap 후에, 기존의 반복자가 같은 위치를 계속 가리키고 있으므로 다른 값을 가리킬 수 도 있습니다.

생성자

std::arrayAggregate 타입 이기 때문에 aggregate 초기화 방식을 사용할 수 있습니다. 이 말은 즉슨, 아래와 같이 배열을 초기화 할 수 있다는 의미 입니다.

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

소멸자

배열 소멸 시에 모든 원소들의 소멸자를 호출합니다.

대입 연산자 (operator=)

배열의 각각의 원소들에 대해 대입 연산자를 호출합니다. 쉽게 말해

std::array<int, 3> a = {1, 2, 3};
std::array<int, 3> b;

b = a;  // b 에 {1,2,3} 이 들어간다

가 됩니다. 반면에 C 배열의 경우

int arr[3] = {1, 2, 3};
int b[3];

b = arr;  // <-- 불가능!!

위와 같은 작업은 불가능 하고 memcpy 와 같은 함수를 통해 복사를 해줘야 합니다.

멤버 접근 함수

at, operator[]

두 함수 모두 인자로 전달한 위치에 있는 원소의 레퍼런스를 리턴합니다. 다만 at() 의 경우 인덱스의 위치를 체크해서 인덱스가 배열의 범위를 벗어난다면 예외를 throw 합니다.

#include <array>
#include <iostream>

int main() {
  std::array<int, 6> data = {1, 2, 4, 5, 5, 6};

  // Set element 1
  data.at(1) = 88;

  // Read element 2
  std::cout << "인덱스 2 에 위치한 원소 : " << data.at(2) << '\n';

  std::cout << "data 배열의 크기 = " << data.size() << '\n';

  try {
    // Set element 6
    data.at(6) = 666;
  } catch (std::out_of_range const& exc) {
    std::cout << "예외 발생 : " << exc.what() << '\n';
  }

  // Print final values
  std::cout << "data:";
  for (int elem : data) std::cout << " " << elem;
  std::cout << '\n';
}

성공적으로 컴파일 하였다면

실행 결과

인덱스 2 에 위치한 원소 : 4
data 배열의 크기 = 6
예외 발생 : array::at: __n (which is 6) >= _Nm (which is 6)
data: 1 88 4 5 5 6

위와 같이 data.at(6) 처럼 배열의 크기를 벗어나는 원소를 참조할 경우 std::out_of_range 예외가 발생합니다.

반면에 operator[] 의 경우 C 의 배열 처럼 배열의 범위를 따로 체크하지 않습니다. 따라서 at 에 비해서 속도가 살짝 빠를 수 있지만, 범위 밖에 원소를 참조하는 일은 정의되지 않은 작업 입니다.

front, back

각각 첫 번째와 마지막 원소의 참조자를 리턴합니다. 만일 배열의 크기가 0 이라면, 해당 함수를 호출하는 작업은 정의되지 않은 작업 입니다.

data

std::array 가 참조하고 있는 배열의 시작 주소값을 리턴합니다.

#include <array>
#include <cstddef>
#include <iostream>

void pointer_func(const int* p, std::size_t size) {
  std::cout << "data = ";
  for (std::size_t i = 0; i < size; ++i) std::cout << p[i] << ' ';
  std::cout << '\n';
}

int main() {
  std::array<int, 4> container{1, 2, 3, 4};

  pointer_func(container.data(), container.size());
}

성공적으로 컴파일 하였다면

실행 결과

data = 1 2 3 4

와 같이 나옵니다. 만일 비어 있지 않은 배열이라면 리턴한 포인터의 경우 첫 번째 원소의 주소값과 같습니다. 반면에 크기가 0 인 배열의 경우 data() 의 리턴값이 nullptr 일 수 도, 아닐 수 도 있습니다.

반복자들

begin, cbegin

시작점을 나타내는 반복자를 리턴합니다. 참고로 cbegin 의 경우 상수 반복자를 리턴. 보통 begin() 을 역참조 하게 되면 첫 번째 원소를 가리키게 됩니다.

end, cend

끝을 나타내는 반복자를 리턴합니다. 보통 end() 의 경우 맨 마지막 원소 바로 다음을 나타냅니다.

rbegin, crbegin

역참조 반복자의 시작점을 리턴합니다. 역참조시에 보통 맨 마지막 원소를 나타내게 됩니다.

rend, crend

역참조 반복자의 끝점을 리턴합니다.

#include <array>
#include <iostream>

int main() {
  std::array<int, 6> data = {1, 2, 4, 5, 5, 6};

  std::cout << "정방향 반복자 : ";
  for (auto itr = data.cbegin(); itr != data.cend(); ++itr) {
    std::cout << *itr << " ";
  }
  std::cout << std::endl;

  std::cout << "역방향 반복자 : ";
  for (auto itr = data.crbegin(); itr != data.crend(); ++itr) {
    std::cout << *itr << " ";
  }
  std::cout << std::endl;
}

성공적으로 컴파일 하였따면

실행 결과

정방향 반복자 : 1 2 4 5 5 6 
역방향 반복자 : 6 5 5 4 2 1 

와 같이 잘 나옵니다. 또한 반복자를 이용해서 algorithm 라이브러리의 여러 함수들을 모두 이용할 수 있습니다. 예를 들어서

#include <algorithm>
#include <array>
#include <iostream>

int main() {
  std::array<int, 6> data = {3, 1, 5, 2, 6, 4};
  std::sort(data.begin(), data.end());

  // 마찬가지로 begin() 과 end() 가 지원되므로 range for 을 사용할 수 있습니다.
  for (int d : data) {
    std::cout << d << " ";
  }
}

성공적으로 컴파일 했다면

실행 결과

1 2 3 4 5 6

와 같이 잘 정렬되서 나옵니다.

크기 관련

empty

크기가 0 인지 아닌지 확인 합니다.

std::array<int, 4> arr;
arr.empty();  // false

std::array<int, 0> brr;
brr.empty();  // true

size

배열의 원소의 개수 (N) 를 리턴합니다.

max_size

배열의 최대 원소 개수를 리턴합니다. 사실 배열의 경우 sizemax_size 둘 다 N 을 리턴합니다. 왜냐하면 배열의 크기는 바뀔 수 없기 때문이죠.

여러가지 작업 관련

fill

배열의 원소들을 인자로 전달된 값으로 채웁니다.

std::array<int, 5> arr;
arr.fill(3);  // arr 은 {3,3,3,3,3}

swap

두 배열의 내용을 바꿉니다.

참고 자료

  • vector : std::array 와는 다르게 데이터 컨테이너의 크기를 줄이거나 늘릴 수 있습니다.

첫 댓글을 달아주세요!
프로필 사진 없음
강좌에 관련 없이 궁금한 내용은 여기를 사용해주세요