std::string_view는 C++17에서 자주 추천되는 타입입니다.
문자열을 복사하지 않고 함수에 넘길 수 있고, std::string, string literal, char buffer를 하나의 인터페이스로 받을 수 있기 때문입니다.
하지만 std::string_view는 편리한 만큼 위험한 타입이기도 합니다.
이 타입은 문자열을 소유하지 않고, 단지 어떤 문자 배열을 바라보는 view일 뿐입니다.
std::string_view의 핵심은 성능이 아니라 ownership을 갖지 않는다는 점입니다. lifetime을 보장하지 못하면 빠른 코드가 아니라 위험한 코드가 됩니다.
이번 글에서는 std::string_view가 무엇을 저장하는지, 어떤 상황에서 안전한지, dangling view가 왜 생기는지, 그리고 실무 코드에서 어떤 기준으로 써야 하는지 정리해보겠습니다.
std::string_view는 문자열 데이터를 복사하지 않고 pointer와 length로 참조하는 lightweight view입니다.
1. std::string_view는 무엇인가?
std::string_view는 문자열 데이터를 소유하지 않는 view type입니다.
대부분의 구현에서 내부적으로 pointer와 length에 가까운 정보를 들고 있다고 생각하면 이해하기 쉽습니다.
중요한 점은 std::string_view가 메모리를 할당하지도 않고, 문자열을 복사하지도 않고, 문자열의 lifetime을 연장하지도 않는다는 것입니다.
| 타입 |
소유권 |
길이 정보 |
주요 용도 |
| std::string |
문자열 데이터 소유 |
있음 |
저장, 수정, lifetime 관리 |
| const char* |
소유하지 않음 |
없음, null terminator에 의존 |
C API, string literal 참조 |
| std::string_view |
소유하지 않음 |
있음 |
읽기 전용 함수 인자, parsing, substring view |
std::string_view는 읽기 전용 관찰자에 가깝습니다.
그래서 function parameter로는 매우 유용하지만, member variable이나 long-lived object에 저장할 때는 훨씬 조심해야 합니다.
2. 왜 중요한가?
문자열은 실무 코드에서 생각보다 자주 복사됩니다.
log tag, request path, user id, config key, protocol token처럼 작은 문자열도 요청 경로마다 반복해서 전달될 수 있습니다.
이때 함수가 문자열을 수정하거나 저장하지 않고 읽기만 한다면 std::string_view는 불필요한 allocation과 copy를 줄이는 좋은 선택이 될 수 있습니다.
하지만 다음 두 조건을 구분하지 못하면 버그가 생깁니다.
- 함수가 호출되는 동안만 문자열을 읽는가?
- 함수 호출 이후에도 view를 저장하거나 비동기 작업에서 사용하는가?
첫 번째라면 std::string_view가 잘 맞는 경우가 많습니다.
두 번째라면 std::string으로 복사해 ownership을 가져오거나, 원본 객체의 lifetime을 명확히 보장해야 합니다.
std::string_view 문제는 컴파일 에러로 잡히지 않는 경우가 많기 때문에 더 위험합니다.
3. 핵심 개념: view는 owner가 아니다
std::string_view를 안전하게 쓰려면 owner와 view를 분리해서 생각해야 합니다.
owner는 실제 문자열 데이터를 가진 객체입니다.
view는 그 데이터를 잠깐 바라보는 작은 객체입니다.
view가 살아 있어도 owner가 사라지면 view는 더 이상 유효하지 않습니다.
std::string_view는 owner의 문자 배열을 바라볼 뿐입니다. owner가 사라지거나 데이터가 바뀌면 view의 안전성도 함께 사라질 수 있습니다.
이 구조 때문에 std::string_view는 reference나 pointer와 비슷한 주의가 필요합니다.
std::string_view라는 이름 때문에 std::string의 안전한 축약형처럼 보이지만, 실제로는 ownership이 없는 view라는 점이 더 중요합니다.
4. C++17 코드로 확인하기
다음 코드는 std::string_view가 함수 인자로 쓰일 때 안전하고 유용한 예입니다.
함수는 문자열을 저장하지 않고 호출 중에만 읽습니다.
#include <iostream>
#include <string>
#include <string_view>
void print_token(std::string_view token)
{
std::cout << "token: " << token << '\n';
}
int main()
{
std::string user_token = "user-42";
print_token(user_token);
print_token("system");
std::string_view view = user_token;
std::string owned_copy(view);
user_token.clear();
std::cout << "copy after clear: " << owned_copy << '\n';
}
실행 결과
token: user-42
token: system
copy after clear: user-42
print_token은 std::string도 받고 string literal도 받을 수 있습니다.
이 과정에서 문자열을 새로 복사하지 않습니다.
반면 owned_copy는 std::string이므로 user_token을 clear한 뒤에도 값을 유지합니다.
view와 copy의 차이는 여기서 분명해집니다.
5. dangling view가 생기는 코드
가장 위험한 패턴은 local std::string에서 만든 std::string_view를 밖으로 반환하거나 저장하는 것입니다.
아래 코드는 컴파일될 수 있지만, 반환된 view는 이미 사라진 local object의 내부 버퍼를 바라보게 됩니다.
#include <string>
#include <string_view>
std::string_view make_bad_view()
{
std::string local = "temporary-token";
return std::string_view(local); // dangling view
}
상태 변화
make_bad_view 진입
local std::string 생성
std::string_view가 local의 내부 버퍼를 가리킴
make_bad_view 종료
local 파괴
반환된 std::string_view는 dangling 상태가 됨
이런 코드는 테스트에서 우연히 정상처럼 보일 수 있습니다.
하지만 이미 lifetime이 끝난 메모리를 읽는 것이므로 undefined behavior입니다.
함수 밖으로 값을 넘겨야 한다면 std::string을 반환하는 편이 맞습니다.
#include <string>
std::string make_token()
{
std::string local = "temporary-token";
return local;
}
예상 결과
std::string이 값을 소유해서 반환됨
copy elision 또는 move를 통해 효율적으로 반환될 수 있음
호출자는 안전하게 문자열 lifetime을 가진 값을 받음
Modern C++에서는 std::string 반환을 지나치게 두려워할 필요가 없습니다.
안전한 ownership이 필요한 곳에서는 std::string을 쓰고, 호출 중 읽기만 하는 곳에서는 std::string_view를 쓰는 식으로 역할을 나누는 편이 좋습니다.
6. 언제 쓰면 좋은가?
std::string_view가 가장 자연스러운 위치는 함수 인자입니다.
특히 함수가 문자열을 읽기만 하고 저장하지 않는다면 좋은 선택지가 됩니다.
| 상황 |
추천 타입 |
이유 |
| 문자열을 읽기만 하는 함수 인자 |
std::string_view |
copy 없이 std::string과 string literal을 모두 받을 수 있음 |
| 문자열을 저장해야 하는 member |
std::string |
object가 값의 lifetime을 직접 관리해야 함 |
| 비동기 작업으로 넘기는 값 |
std::string |
호출자가 사라진 뒤에도 값이 필요할 수 있음 |
| 큰 문자열을 parsing하며 조각을 임시로 보는 경우 |
std::string_view |
원본 buffer가 살아 있는 동안 substring copy를 줄일 수 있음 |
| C API와 직접 맞물리는 null-terminated 문자열 |
const char* 또는 std::string |
std::string_view는 null termination을 보장하지 않음 |
std::string_view는 parsing 코드에서도 유용합니다.
긴 request line이나 config line을 여러 token으로 나눌 때 각 token을 std::string으로 복사하지 않고 view로 다룰 수 있습니다.
다만 이 경우에도 원본 buffer가 token view보다 오래 살아야 합니다.
7. 자주 하는 오해
- std::string_view는 더 빠른 std::string이다.
std::string_view는 빠른 string이 아니라 non-owning view입니다. 저장과 lifetime 관리가 필요하면 std::string이 맞습니다.
- std::string_view는 항상 null-terminated이다.
std::string_view는 length를 따로 가지므로 중간 substring도 표현할 수 있습니다. 따라서 C API에 넘길 때 null terminator를 기대하면 안 됩니다.
- string literal에서 만든 view만 안전하다.
string literal은 program lifetime 동안 살아 있으므로 안전한 편입니다. 하지만 std::string owner가 view보다 오래 살아도 안전하게 사용할 수 있습니다.
- const std::string&를 모두 std::string_view로 바꾸면 된다.
함수가 문자열을 저장하거나 c_str() null termination에 의존한다면 단순 치환은 위험합니다.
- std::string_view를 member로 쓰면 메모리를 아낄 수 있으니 좋다.
owner lifetime을 class 밖에서 강하게 보장할 수 있는 특수한 경우가 아니라면 member에는 std::string이 더 안전합니다.
std::string_view를 잘 쓰는 기준은 API boundary에서 ownership이 어디에 있는지 드러내는 것입니다.
소유하지 않는다는 사실을 타입만 보고도 알 수 있어야 하고, 그 view가 언제까지 유효한지도 코드 구조에서 확인 가능해야 합니다.
8. 실무에서는 어떻게 볼까?
실무에서는 std::string_view를 성능 최적화 도구보다 API 설계 도구로 보는 편이 좋습니다.
함수가 입력 문자열을 읽기만 한다는 의도를 표현하고, 호출자가 std::string이든 string literal이든 편하게 넘길 수 있게 해주기 때문입니다.
반대로 class가 값을 오래 들고 있어야 한다면 std::string_view는 의도를 흐릴 수 있습니다.
cache key, user id, file path, request path를 object 내부에 보관한다면 복사 비용보다 lifetime bug 비용이 훨씬 큽니다.
성능이 정말 문제라면 먼저 profiling으로 문자열 copy가 병목인지 확인하고, 그 다음 owner를 어디에 둘지 설계해야 합니다.
특히 async callback, thread handoff, event queue, logging buffer처럼 실행 시점이 밀릴 수 있는 구조에서는 view를 넘기는 순간부터 위험해질 수 있습니다.
| 코드 리뷰 질문 |
확인해야 할 것 |
| 이 view의 owner는 어디인가? |
std::string, string literal, 외부 buffer 중 무엇이 실제 데이터를 들고 있는지 확인 |
| view가 owner보다 오래 살 가능성이 있는가? |
return, member 저장, lambda capture, async queue 전달 여부 확인 |
| null terminator가 필요한 API로 넘어가는가? |
C API나 printf류 함수에 넘기기 전 std::string 변환 필요 여부 확인 |
| 원본 문자열이 중간에 수정되는가? |
reallocation, clear, resize, assign 뒤 view 사용 여부 확인 |
std::string_view는 잘 쓰면 API가 가벼워지고, 잘못 쓰면 ownership bug가 조용히 숨어듭니다.
그래서 팀 코드에서는 “저장하지 않는 인자에는 string_view, 저장하는 값에는 string” 같은 단순한 규칙을 먼저 세우는 것이 효과적입니다.
9. 정리
std::string_view는 문자열 데이터를 소유하지 않는 non-owning view입니다.
함수 호출 중 읽기만 하는 인자에는 copy를 줄이는 좋은 선택지가 될 수 있습니다.
view가 owner보다 오래 살면 dangling view가 되고 undefined behavior로 이어질 수 있습니다.
std::string_view는 null termination을 보장하지 않으므로 C API와 연결할 때 주의해야 합니다.
저장, 비동기 전달, ownership 관리가 필요한 값에는 std::string을 사용하는 편이 안전합니다.
std::string_view를 쓸지 말지 판단하는 기준은 “복사를 줄일 수 있는가”보다 “원본 문자열의 lifetime을 확실히 보장할 수 있는가”입니다.