빠른 비교
#include <string_view>
#include <span>
void printName(std::string_view name) { // 복사 없이 모든 문자열 타입 수용
std::cout << name << "\n";
}
void sumScores(std::span<const int> scores) { // vector, array, 배열 모두 수용
int sum = 0;
for (int s : scores) sum += s;
std::cout << sum << "\n";
}
printName("Alice"); // 문자열 리터럴
printName(std::string("Bob")); // std::string
std::vector<int> v{1, 2, 3};
sumScores(v); // vectorstring_view와 span은 복사 없이 "보기만 하는 창"입니다. 핵심은 빠르다는 점보다 소유하지 않는다는 점입니다.
갈리는 기준
먼저 다시 보는 기본형은 아래입니다.
- 문자열 읽기 전용 뷰:
std::string_view - 연속 메모리 뷰:
std::span<T> - 부분 구간:
substr,subspan - 수명은 원본이 책임짐
어떤 view 타입을 먼저 고르면 되나
| 상황 | 먼저 떠올릴 것 |
|---|---|
| 문자열을 읽기만 함 | std::string_view |
| 연속 메모리를 읽기만 함 | std::span<const T> |
| 연속 메모리를 수정해야 함 | std::span<T> |
| 소유가 필요함 | std::string, std::vector<T> |
string_view — const string& 대신 쓰는 이유
const std::string&는 char* 또는 문자열 리터럴을 받을 때 임시 std::string을 생성해 복사가 발생할 수 있습니다. string_view는 포인터와 길이만 저장하는 non-owning view로, 소유권 복사 없이 std::string, const char*, 문자열 리터럴, 다른 string_view를 모두 수용합니다.
// const string& — 리터럴을 받으면 임시 string 생성
void process(const std::string& s) { ... }
process("hello"); // ⚠️ std::string 임시 객체 생성 후 참조
// string_view — 복사 없이 포인터+길이만 전달
void process(std::string_view sv) { ... }
process("hello"); // ✅ 복사 없음
process(std::string("hello")); // ✅ 호출 동안만 유효한 임시 string도 인자로는 가능
process(sv.substr(0, 3)); // ✅ 새 string_view 반환 (복사 아님)span — 연속 메모리를 통일된 인터페이스로
std::span<T>는 연속 메모리를 가리키는 non-owning view입니다. vector, array, C 배열을 모두 같은 함수 인자로 받을 수 있습니다. std::span<const T>는 읽기 전용, std::span<T>는 수정 가능합니다.
void scale(std::span<int> data, int factor) {
for (int& x : data) x *= factor;
}
std::vector<int> v{1, 2, 3};
std::array<int, 3> a{4, 5, 6};
int arr[] = {7, 8, 9};
scale(v, 2); // ✅ vector
scale(a, 2); // ✅ array
scale(arr, 2); // ✅ C 배열
// 부분 구간 전달
scale(std::span<int>(v).subspan(1, 2), 10); // v[1], v[2]만string_view — 탐색 연산
string_view도 std::string처럼 find, substr, starts_with 등을 지원합니다.
std::string_view sv = "Hello, World!";
// 검색
auto pos = sv.find("World"); // 7 (없으면 npos)
auto pos2 = sv.find_first_of("aeiou"); // 첫 번째 모음 위치
// 부분 문자열 (복사 없음 — 새 view 반환)
auto sub = sv.substr(7, 5); // "World"
// C++20 접두사/접미사 확인
bool starts = sv.starts_with("Hello"); // true
bool ends = sv.ends_with("!"); // true
// 길이와 비교
sv.size(); // 13
sv.empty(); // false
sv == "Hello, World!"; // true (문자열 비교 가능)span — static extent vs dynamic extent
std::span<T, N>은 컴파일 타임에 크기가 고정됩니다. std::span<T>(= std::span<T, std::dynamic_extent>)는 런타임 크기입니다.
int arr[5] = {1, 2, 3, 4, 5};
// static extent — 크기가 컴파일 타임에 고정
std::span<int, 5> fixed{arr}; // 크기 5로 고정
// std::span<int, 3> wrong{arr}; // ❌ 크기 불일치 — 컴파일 오류
// dynamic extent — 런타임 크기 (기본)
std::span<int> dynamic{arr, 5}; // 크기 런타임 결정
std::span<int> partial{arr, 3}; // 앞 3개만
// subspan: 부분 구간
auto sub = dynamic.subspan(1, 3); // arr[1..3]
auto first2 = dynamic.first(2); // arr[0..1]
auto last2 = dynamic.last(2); // arr[3..4]non-owning view — 수명 관리 책임
string_view와 span은 원본을 소유하지 않습니다. 원본보다 오래 살면 dangling view가 됩니다.
// ❌ 임시 string의 view를 반환 — dangling
std::string_view dangerous() {
std::string temp = "hello";
return temp; // temp가 파괴되면 view는 무효
}
// ✅ 원본 수명이 더 길 때만 view 사용
std::string_view safe(const std::string& s) {
return s; // 호출자의 string을 가리킴 — 호출자 책임
}// ❌ 문자열을 소유하지 않는 view를 컨테이너에 오래 저장
std::vector<std::string_view> names;
names.push_back(std::string("Alice")); // 즉시 dangling
// ✅ 오래 저장할 값이면 owning 타입 사용
std::vector<std::string> names;
names.push_back(std::string("Alice"));체크포인트
| 상황 | 적합한 선택 |
|---|---|
| 읽기 전용 문자열 인자 (복사 없이) | std::string_view |
| 문자열 소유가 필요 | std::string |
| 읽기 전용 연속 배열 인자 | std::span<const T> |
| 수정 가능한 연속 배열 인자 | std::span<T> |
| 부분 구간 전달 | sv.substr() / span.subspan() |
| 문자열 검색 | sv.find(str) — 없으면 npos |
| 접두사/접미사 확인 (C++20) | sv.starts_with() / sv.ends_with() |
| 컴파일 타임 크기 고정 span | std::span<T, N> (static extent) |
| 런타임 크기 span (기본) | std::span<T> (dynamic extent) |
주의할 점
string_view나 span을 반환값이나 멤버로 저장할 때는 원본 수명을 함께 설계해야 합니다. 원본보다 오래 살면 dangling이 됩니다.
// ❌ 임시 string에서 string_view 생성 — 즉시 dangling
std::string_view sv = std::string("hello"); // 임시 객체 즉시 파괴
std::cout << sv; // UB
// ⚠ string_view를 클래스 멤버로 저장 — 원본 수명을 같이 보장해야 함
struct Config {
std::string_view name; // 원본이 더 오래 살아야 안전
};
// ✅ 소유가 필요한 상황에서는 string 사용
struct Config {
std::string name; // ✅ 자체 소유
};
// ✅ 함수 인자나 짧은 지역 뷰로 쓰면 수명 추적이 쉬움
void process(std::string_view sv) {
// sv의 원본은 호출자 측에서 보장
std::cout << sv.substr(0, 3);
}선택 기준
- 문자열을 읽기만 하고 소유는 안 한다:
string_view - 연속 메모리를 타입 안전하게 본다:
span - 부분 구간 전달에 유리하다
- 원본보다 오래 살면 dangling
- 저장이 필요하면
string,vector같은 owning 타입을 쓴다
참고 링크
2 sources