----------------
title : C++ 레퍼런스 - std::array (안전한 배열)
cat_title : std::array
ref_title : std::array, array
path : /C++ Reference/array
publish_date : 2020-07-17
----------------

##@ cpp-ref-start

#@ std::array

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

```cpp-formatted
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` 이 0 일 때), `array.begin() == array.end()` 이며, `front()` 나 `back()` 을 호출할 시 그 결과는 정의되지 않습니다 (Undefined behavior).

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

#### 반복자 무효화

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

### 생성자

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

```cpp-formatted
std::array<int, 3> arr = {1, 2, 3};
```

### 소멸자

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

### 대입 연산자 (operator=)

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

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

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

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

```cpp-formatted
int arr[3] = {1, 2, 3};
int b[3];

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

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

### 멤버 접근 함수

#### at, operator[]

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

```cpp-formatted
#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';
}
```

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

```exec
인덱스 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` 가 참조하고 있는 배열의 시작 주소값을 리턴합니다.

```cpp-formatted
#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());
}
```

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

```exec
data = 1 2 3 4
```

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

### 반복자들

#### begin, cbegin

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

#### end, cend

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

#### rbegin, crbegin

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

#### rend, crend

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

```cpp-formatted
#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;
}
```

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

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

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

```cpp-formatted
#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 << " ";
  }
}
```

성공적으로 컴파일 했다면

```exec
1 2 3 4 5 6
```

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

### 크기 관련

#### empty

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

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

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

#### size

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

#### max_size

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

### 여러가지 작업 관련

#### fill

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

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

#### swap

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

### 참고 자료

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