C++ std::span, 배열을 복사 없이 넘기는 법

std::span은 연속된 원소를 소유하지 않고 가리키는 C++20 뷰이며, 배열이나 컨테이너를 복사 없이 함수에 넘길 때 씁니다.

이 글은 cppreference의 span 문서를 2026-08-30 기준으로 정리한 일반 설명이며, 표준 버전과 구현에 따라 세부 동작은 달라질 수 있습니다.

std::span은 무엇인가?

한 줄 답: 이 타입은 연속된 원소 시퀀스를 소유하지 않고 가리키는 C++20 뷰입니다.

이 타입은 <span> 헤더에 정의된 C++20 클래스 템플릿이며, 기본 Extentstd::dynamic_extent입니다. 연속된 객체 시퀀스를 가리키고, 그 시퀀스의 첫 원소가 위치 0에 있는 구조를 표현합니다.

정적 extent에서는 원소 수가 컴파일 타임에 알려지고 타입에 인코딩됩니다. 동적 extent에서는 원소 수가 실행 중 범위 정보로 표현되므로, 고정된 길이를 타입으로 나타낼지 실행 중 길이로 다룰지를 구분할 수 있습니다. 이 뷰는 메모리를 소유하지 않습니다.

포인터와 길이 쌍과 무엇이 다른가?

한 줄 답: 이 뷰는 시작 포인터와 길이를 타입·멤버로 묶고, 정적 extent는 길이를 타입에 인코딩합니다.

T*와 원소 수를 별도 인자로 전달하는 방식에서는 포인터와 범위 정보가 호출 규약에 나뉘어 있습니다. 이 방식에서는 data()로 기반 연속 저장소의 시작 포인터를 얻고 size()로 원소 수를 관찰하므로, 범위를 나타내는 정보가 하나의 뷰에 함께 표현됩니다.

extent는 템플릿의 extent를 나타내는 정적 멤버 상수이고, std::dynamic_extent는 정적 extent와 동적 extent를 구분하는 헬퍼 상수입니다. 문서의 exposition 설명에서는 정적 extent 값과 기반 저장소 포인터가 나타나며, 동적 extent일 때만 실행 중 크기를 보관하는 size_가 함께 나타납니다.

연속된 일부만 다룰 때는 first, last, subspan으로 부분 뷰를 얻을 수 있습니다. subspan은 offset과 count로 연속된 원소의 일부를 정하므로, 포인터를 잘라낸 뒤 길이를 별도로 다시 관리하는 형태보다 범위의 의미를 뚜렷하게 나타냅니다.

함수 인자로는 어떻게 쓰나?

한 줄 답: 연속 저장소를 이 뷰 매개변수로 받으면 배열이나 컨테이너를 복사하지 않고 범위를 넘깁니다.

읽기 전용 접근이 목적이라면 원소 타입에 const를 붙인 매개변수를 사용할 수 있습니다. 다음처럼 함수가 범위를 값으로 받으면 함수 안에서 size(), data(), subspan을 통해 전달된 연속 범위를 관찰할 수 있습니다.

#include <span>

void display(std::span<const char> abc) {
    // abc.size(), abc.data(), abc.subspan(...)
}

이 뷰를 값으로 전달하는 것은 뷰가 가진 범위 정보를 전달하는 일이며, 기반 배열이나 컨테이너의 원소를 복사하는 방식이 아닙니다. 공식 예시에서는 C 배열과 std::array를 통해 정적 extent를 얻고, std::vector를 통해 동적 extent를 다루는 형태를 보여 줍니다.

다만 여기서 전제하는 대상은 연속 저장소를 제공하는 시퀀스입니다. 컨테이너라는 이유만으로 모든 종류를 같은 방식으로 넘길 수 있다고 일반화하지 않고, 실제로 연속된 원소를 가진 범위인지 확인해야 합니다.

수명과 소유권에서 주의할 점은 무엇인가?

한 줄 답: 이 뷰는 원본 시퀀스의 수명에 의존하며, 원본의 포인터가 무효화되면 이 뷰의 원소 접근도 무효화됩니다.

원본 배열의 수명이 끝나거나 기반 컨테이너가 파괴되면, 이 뷰가 가리킬 원소도 더 이상 유효한 저장소에 남아 있지 않습니다. 또한 [s.data(), s.data() + s.size()) 범위의 포인터를 무효화하는 연산이 원본에 일어나면, 해당 범위 원소에 대한 포인터·반복자·참조도 무효화됩니다.

따라서 지역 배열이 유효한 범위를 벗어난 뒤 뷰를 보관하거나, 원본 컨테이너를 파괴한 뒤 남은 뷰에 접근해서는 안 됩니다. 원본을 이동한 뒤의 상태에 의존하는 뷰를 남기지 않는 것도 같은 수명 규칙을 지키는 방법입니다.

이 타입에는 std::ranges::enable_borrowed_rangestd::ranges::enable_view가 적용되어 borrowed_rangeview를 만족한다는 특성도 있습니다.

FAQ

한 줄 답: C++20 여부, extent의 형태, 그리고 원본 저장소가 유효한지를 차례로 확인하면 됩니다.

질문답변
C++ 몇 표준부터 쓸 수 있습니까?C++20부터 사용할 수 있습니다.
정적 extent와 동적 extent는 무엇입니까?정적 extent는 원소 수가 컴파일 타임에 알려져 타입에 인코딩됩니다. 동적 extent는 std::dynamic_extent로 표현하며, 실제 원소 수는 실행 중 범위 정보로 다룹니다.
원본 배열이나 컨테이너가 사라지면 어떻게 됩니까?이 뷰는 원본을 소유하지 않으므로, 원본 포인터가 무효화되면 원소 포인터·반복자·참조도 무효화됩니다. 원본 수명이 끝난 뒤에는 뷰를 사용해서는 안 됩니다.
모든 컨테이너를 넘길 수 있습니까?이 글에서 다루는 대상은 연속 시퀀스입니다. 공식 예시는 C 배열, std::array, std::vector를 다루므로 컨테이너라는 이유만으로 모든 종류를 지원한다고 일반화할 수 없습니다.

출처

한 줄 답: 본문 사실과 짧은 코드 형태는 2026-08-30 기준으로 확인한 cppreference 문서만 사용합니다.

  • span — 클래스 템플릿 정의, extent, 무효화 규칙, 범위 특성, 함수 인자 예시를 확인했습니다.
  • dynamic_extent — 정적·동적 extent 구분과 비소유 뷰 설명을 확인했습니다.
  • span::size — 뷰의 원소 수를 반환하는 관찰자를 확인했습니다.
  • span::data — 기반 연속 저장소의 시작 포인터를 반환하는 관찰자를 확인했습니다.
  • span::subspan — offset과 count로 연속된 부분 뷰를 얻는 기능을 확인했습니다.