C++/Concepts

C++ std::string_view 사용법과 lifetime 주의점

Enchantée 2026. 7. 6. 22:18
728x90
반응형

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를 줄이는 좋은 선택이 될 수 있습니다.

하지만 다음 두 조건을 구분하지 못하면 버그가 생깁니다.

  1. 함수가 호출되는 동안만 문자열을 읽는가?
  2. 함수 호출 이후에도 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. 자주 하는 오해

  1. std::string_view는 더 빠른 std::string이다.
    std::string_view는 빠른 string이 아니라 non-owning view입니다. 저장과 lifetime 관리가 필요하면 std::string이 맞습니다.
  2. std::string_view는 항상 null-terminated이다.
    std::string_view는 length를 따로 가지므로 중간 substring도 표현할 수 있습니다. 따라서 C API에 넘길 때 null terminator를 기대하면 안 됩니다.
  3. string literal에서 만든 view만 안전하다.
    string literal은 program lifetime 동안 살아 있으므로 안전한 편입니다. 하지만 std::string owner가 view보다 오래 살아도 안전하게 사용할 수 있습니다.
  4. const std::string&를 모두 std::string_view로 바꾸면 된다.
    함수가 문자열을 저장하거나 c_str() null termination에 의존한다면 단순 치환은 위험합니다.
  5. 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을 확실히 보장할 수 있는가”입니다.

 


728x90
반응형