[2026] C++ span 기초 | 연속 메모리 뷰 가이드
이 글의 핵심
C++ span 기초: 연속 메모리 뷰 가이드. span이란?·기본 사용.
span이란?
std::span 은 C++20에서 도입된 연속 메모리 영역에 대한 경량 뷰입니다. 배열, 벡터, 또는 연속된 메모리를 가리키는 포인터와 크기를 함께 제공하여, 안전하고 통합된 인터페이스를 제공합니다. 아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <span>
// 실행 예제
void process(std::span<int> data) {
for (int x : data) {
std::cout << x << " ";
}
}
int arr[] = {1, 2, 3};
std::vector<int> vec = {4, 5, 6};
process(arr); // 배열
process(vec); // 벡터
왜 필요한가?:
- 통합 인터페이스: 배열, 벡터, C 배열을 하나의 타입으로 처리
- 안전성: 크기 정보를 포함하여 경계 검사 가능
- 성능: 복사 없이 참조만 (포인터 + 크기)
- 간결성: 포인터와 크기를 별도로 전달할 필요 없음 다음은 cpp를 활용한 상세한 구현 코드입니다. 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ 전통적 방식: 포인터 + 크기 (불편, 오류 가능)
// 실행 예제
void process(int* data, size_t size) {
for (size_t i = 0; i < size; ++i) {
std::cout << data[i] << " ";
}
}
int arr[] = {1, 2, 3};
process(arr, 3); // 크기를 수동으로 전달
// ✅ span: 통합되고 안전함
void process(std::span<int> data) {
for (int x : data) { // 범위 기반 for 사용 가능
std::cout << x << " ";
}
}
process(arr); // 크기 자동 추론
span의 특성:
- 비소유(Non-owning): 메모리를 소유하지 않고 참조만 함
- 경량: 포인터와 크기만 저장 (보통 16바이트)
- 복사 가능: 복사 비용이 매우 낮음
- 뷰: 원본 데이터를 수정 가능 (const span은 읽기 전용) 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// 실행 예제
std::vector<int> vec = {1, 2, 3, 4, 5};
std::span<int> sp{vec};
sp[0] = 10; // 원본 vec 수정
std::cout << vec[0]; // 10 (수정됨)
span의 구조: 다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// 개념적 구현
template<typename T>
class span {
T* data_;
size_t size_;
public:
span(T* data, size_t size) : data_(data), size_(size) {}
size_t size() const { return size_; }
T* data() const { return data_; }
T& operator const { return data_[index]; }
// 반복자
T* begin() const { return data_; }
T* end() const { return data_ + size_; }
};
기본 사용
아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <span>
#include <vector>
std::vector<int> v = {1, 2, 3, 4, 5};
// 전체 span
std::span<int> sp1{v};
// 부분 span
std::span<int> sp2{v.data() + 1, 3}; // {2, 3, 4}
// 크기
std::cout << sp1.size() << std::endl; // 5
실전 예시
예시 1: 함수 매개변수
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <span>
#include <vector>
#include <array>
// 통합 인터페이스
void printData(std::span<const int> data) {
for (int x : data) {
std::cout << x << " ";
}
std::cout << std::endl;
}
int main() {
int arr[] = {1, 2, 3};
std::vector<int> vec = {4, 5, 6};
std::array<int, 3> stdArr = {7, 8, 9};
printData(arr); // 1 2 3
printData(vec); // 4 5 6
printData(stdArr); // 7 8 9
}
예시 2: 부분 범위
아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <span>
std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 처음 5개
std::span<int> first5{v.data(), 5};
// 마지막 5개
std::span<int> last5{v.data() + 5, 5};
// subspan
std::span<int> sp{v};
auto middle = sp.subspan(3, 4); // {4, 5, 6, 7}
예시 3: 경계 검사
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <span>
void safeAccess(std::span<int> data, size_t index) {
// 경계 검사
if (index < data.size()) {
std::cout << data[index] << std::endl;
} else {
std::cout << "범위 초과" << std::endl;
}
}
int main() {
std::vector<int> v = {1, 2, 3};
safeAccess(v, 1); // 2
safeAccess(v, 10); // 범위 초과
}
예시 4: 2D 배열
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <span>
void processMatrix(std::span<int> data, size_t rows, size_t cols) {
for (size_t i = 0; i < rows; ++i) {
for (size_t j = 0; j < cols; ++j) {
std::cout << data[i * cols + j] << " ";
}
std::cout << std::endl;
}
}
int main() {
std::vector<int> matrix = {
1, 2, 3,
4, 5, 6,
7, 8, 9
};
processMatrix(matrix, 3, 3);
}
span 연산
다음은 cpp를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
std::span<int> sp{v};
// 크기
auto size = sp.size();
auto bytes = sp.size_bytes();
// 접근
int first = sp.front();
int last = sp.back();
int* ptr = sp.data();
// 부분 범위
auto sub1 = sp.first(3); // 처음 3개
auto sub2 = sp.last(3); // 마지막 3개
auto sub3 = sp.subspan(2, 3); // [2]부터 3개
자주 발생하는 문제
문제 1: 수명
아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ 댕글링
std::span<int> getDanglingSpan() {
std::vector<int> v = {1, 2, 3};
return std::span{v};
// v 소멸
}
// ✅ 참조 명확화
std::span<int> getSpan(std::vector<int>& v) {
return std::span{v};
}
문제 2: const
아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
std::vector<int> v = {1, 2, 3};
// 읽기 전용
std::span<const int> sp{v};
// ❌ 수정 불가
// sp[0] = 10; // 에러
// ✅ 수정 가능
std::span<int> sp2{v};
sp2[0] = 10;
문제 3: 크기
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// 고정 크기 span
std::span<int, 3> fixedSpan{arr};
// 동적 크기 span
std::span<int> dynamicSpan{vec};
// ❌ 크기 불일치
// std::span<int, 5> sp{arr}; // arr 크기가 3이면 에러
문제 4: 포인터 변환
아래 코드는 cpp를 사용한 구현 예제입니다. 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
int* ptr = getData();
size_t size = getSize();
// ✅ span으로 래핑
std::span<int> sp{ptr, size};
// 안전한 접근
for (int x : sp) {
std::cout << x << " ";
}
span vs 포인터
아래 코드는 cpp를 사용한 구현 예제입니다. 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ 포인터 (크기 정보 없음)
void process(int* data, size_t size) {
for (size_t i = 0; i < size; ++i) {
std::cout << data[i] << " ";
}
}
// ✅ span (크기 포함)
void process(std::span<int> data) {
for (int x : data) {
std::cout << x << " ";
}
}
실무 패턴
패턴 1: 읽기 전용 뷰
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class DataProcessor {
public:
// const span: 읽기 전용
double calculateAverage(std::span<const double> data) const {
if (data.empty()) return 0.0;
double sum = 0.0;
for (double value : data) {
sum += value;
}
return sum / data.size();
}
};
// 사용
std::vector<double> values = {1.0, 2.0, 3.0, 4.0, 5.0};
DataProcessor processor;
double avg = processor.calculateAverage(values);
패턴 2: 슬라이딩 윈도우
다음은 cpp를 활용한 상세한 구현 코드입니다. 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
template<typename T>
void processWindows(std::span<T> data, size_t windowSize) {
if (data.size() < windowSize) return;
for (size_t i = 0; i <= data.size() - windowSize; ++i) {
auto window = data.subspan(i, windowSize);
// 윈도우 처리
std::cout << "Window [" << i << "]: ";
for (const auto& value : window) {
std::cout << value << " ";
}
std::cout << '\n';
}
}
// 사용
std::vector<int> data = {1, 2, 3, 4, 5, 6, 7, 8};
processWindows(std::span{data}, 3);
패턴 3: 버퍼 래퍼
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class Buffer {
std::vector<uint8_t> data_;
public:
Buffer(size_t size) : data_(size) {}
// 전체 버퍼 뷰
std::span<uint8_t> asSpan() {
return std::span{data_};
}
// 읽기 전용 뷰
std::span<const uint8_t> asSpan() const {
return std::span{data_};
}
// 부분 뷰
std::span<uint8_t> slice(size_t offset, size_t length) {
return std::span{data_}.subspan(offset, length);
}
};
// 사용
Buffer buffer(1024);
auto view = buffer.asSpan();
view[0] = 0xFF;
FAQ
Q1: span은 무엇인가요?
A: C++20의 연속 메모리 영역에 대한 경량 뷰입니다. 포인터와 크기를 함께 제공하여 안전하고 통합된 인터페이스를 제공합니다. 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
std::vector<int> vec = {1, 2, 3, 4, 5};
std::span<int> sp{vec}; // 포인터 + 크기
std::cout << sp.size() << '\n'; // 5
std::cout << sp[0] << '\n'; // 1
Q2: span은 데이터를 복사하나요?
A: 아니요. span은 비소유 뷰로, 원본 데이터를 참조만 합니다. 복사 비용이 매우 낮습니다 (포인터 + 크기만 복사). 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
std::vector<int> vec(1000000); // 큰 벡터
// span 생성: 복사 없음 (포인터 + 크기만)
std::span<int> sp{vec};
// span 복사: 매우 빠름 (16바이트만 복사)
std::span<int> sp2 = sp;
Q3: span은 어디에 사용하나요?
A:
- 함수 매개변수: 배열, 벡터, C 배열을 통합
- 부분 범위: 슬라이싱, 윈도우 처리
- 안전한 포인터: 크기 정보 포함으로 경계 검사 가능 아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// 통합 인터페이스
void process(std::span<int> data) {
// 배열, 벡터, std::array 모두 가능
}
int arr[] = {1, 2, 3};
std::vector<int> vec = {4, 5, 6};
std::array<int, 3> stdArr = {7, 8, 9};
process(arr);
process(vec);
process(stdArr);
Q4: span의 크기는 고정인가요?
A: 동적 크기와 고정 크기 모두 지원합니다. 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// 동적 크기 (기본)
std::span<int> dynamicSpan{vec};
// 고정 크기 (컴파일 타임)
std::span<int, 3> fixedSpan{arr};
// 고정 크기는 컴파일 타임에 검증됨
// std::span<int, 5> wrongSpan{arr}; // 에러: 크기 불일치
Q5: span의 수명 관리는 어떻게 하나요?
A: span은 비소유 뷰이므로, 원본 데이터의 수명을 주의해야 합니다. 아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ 댕글링 span
std::span<int> getDanglingSpan() {
std::vector<int> vec = {1, 2, 3};
return std::span{vec}; // vec 소멸!
}
// ✅ 안전한 사용
std::span<int> getSpan(std::vector<int>& vec) {
return std::span{vec}; // vec은 호출자가 소유
}
Q6: const span과 span의 차이는?
A:
- const std::span
: span 자체가 const (다른 메모리를 가리킬 수 없음) - std::span
: 가리키는 데이터가 const (데이터 수정 불가) 아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
std::vector<int> vec = {1, 2, 3};
// span<const int>: 데이터 읽기 전용
std::span<const int> sp1{vec};
// sp1[0] = 10; // 에러: 데이터 수정 불가
sp1 = std::span<const int>{}; // OK: span 자체는 수정 가능
// const span<int>: span 자체가 const
const std::span<int> sp2{vec};
sp2[0] = 10; // OK: 데이터 수정 가능
// sp2 = std::span<int>{}; // 에러: span 자체 수정 불가
Q7: span을 포인터로 변환할 수 있나요?
A: 가능합니다. data() 메서드로 포인터를 얻을 수 있습니다.
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
std::span<int> sp{vec};
// 포인터 얻기
int* ptr = sp.data();
// 레거시 API 호출
legacyFunction(ptr, sp.size());
Q8: span 학습 리소스는?
A:
- “C++20 The Complete Guide” by Nicolai Josuttis
- “Effective Modern C++” by Scott Meyers
- cppreference.com - std::span
관련 글: string_view, array, vector.
한 줄 요약:
std::span은 연속 메모리 영역에 대한 경량 비소유 뷰로, 안전하고 통합된 인터페이스를 제공합니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.