메인 콘텐츠로 건너뛰기

Modern C++ 성능 최적화의 숨은 보석 std::string_view

std::string_view는 데이터를 직접 소유하거나 복사하지 않고 그저 특정 메모리 영역을 바라보기만 하는 창문 역할을 합니다.

2026년 1월 8일
c++
Modern C++ 성능 최적화의 숨은 보석 std::string_view - 아티클 커버 이미지

프로그래밍을 하다 보면 우리는 아주 당연하게 여기던 관습이 사실은 성능의 발목을 잡고 있었다는 사실을 깨닫곤 합니다. C++에서 문자열을 다룰 때도 마찬가지입니다. 여러분은 함수에 문자열을 전달할 때 보통 어떤 타입을 사용하시나요? 아마 많은 분이 const std::string&를 가장 먼저 떠올리실 겁니다. 하지만 우리가 효율적이라고 굳게 믿었던 이 방식 뒤에는 의외의 성능 복병이 숨어 있습니다. 오늘은 C++17에서 등장하여 성능 최적화의 새로운 지평을 연 std::string_view를 함께 살펴보겠습니다.

왜 새로운 방식이 필요했을까요

우리가 const std::string&를 인자로 받는 함수에 "Hello"와 같은 C 스타일의 문자열 리터럴을 전달한다고 가정해 보겠습니다. 이때 우리 눈에는 보이지 않지만 내부에서는 적지 않은 일이 일어납니다. C++ 표준 라이브러리는 이 리터럴을 안전하게 전달하기 위해 임시 std::string 객체를 생성합니다. 이 과정에서 메모리 할당과 데이터 복사가 필연적으로 발생하게 되죠.

짧은 문자열 한두 개라면 큰 문제가 되지 않을 수 있습니다. 하지만 이 함수가 프로그램의 핵심 로직 안에서 초당 수만 번씩 호출되는 상황이라면 어떨까요? 작은 눈덩이가 모여 거대한 눈사태가 되듯 불필요한 메모리 오버헤드는 프로그램 전체의 응답성을 떨어뜨리는 원인이 됩니다.

std::string_view - 문자열을 바라보는 새로운 창문

이러한 문제를 해결하기 위해 탄생한 것이 바로 std::string_view입니다. 이름에서 유추할 수 있듯이 이 기술은 데이터를 직접 소유하거나 복사하지 않고 그저 특정 메모리 영역을 바라보기만 하는 창문 역할을 합니다.

std::string_view의 핵심 구조는 매우 단순합니다. 이 객체는 데이터가 시작되는 메모리의 주소 값과 문자열의 길이라는 단 두 개의 정보만을 가집니다. 데이터를 복사하지 않으니 생성 비용이 거의 없고 소유권을 주장하지 않으니 가볍습니다. 물론 데이터를 수정할 수 없는 읽기 전용이라는 제약이 있지만 우리가 함수 인자로 문자열을 받을 때의 목적이 대부분 읽기라는 점을 고려하면 이는 매우 합리적인 교환입니다.

image

효율성의 차이를 숫자로 확인하기

기존의 방식과 std::string_view가 구체적으로 어떻게 다른지 비교해 보면 그 차이가 더 선명하게 드러납니다.

특징const std::string&std::string_view
메모리 할당리터럴 전달 시 임시 객체 생성 가능전혀 없음
슬라이싱 성능 (Substr)(새로운 문자열 복사 생성)(시작 주소와 길이 정보만 변경)
권장 용도데이터를 소유하고 수명을 관리할 때함수 인자로 전달받아 읽기만 할 때

실전 코드로 보는 활용 예시

실제 개발 환경에서 std::string_view가 어떻게 쓰이는지 코드를 통해 확인해 보겠습니다.

#include <iostream>
#include <string_view>

void printName(std::string_view sv) {
    std::cout << "출력: " << sv << "\n";
}

int main() {
    std::string s = "C++ Modern Programming";
    
    // 1. std::string을 그대로 전달 (복사나 할당 없음)
    printName(s); 
    
    // 2. 문자열 리터럴 전달 (임시 객체 생성 없이 주소만 전달)
    printName("Fast and Light"); 

    // 3. 아주 강력한 슬라이싱 (복사 없이 일부만 보기)
    std::string_view sv = s;
    std::string_view part = sv.substr(0, 3); // "C++" 부분만 O(1)으로 추출
    std::cout << "부분 문자열: " << part << "\n";
}

양날의 검 창문 밖의 주인이 사라진다면

물론 모든 기술에는 명암이 존재하듯 std::string_view 역시 만능 열쇠는 아닙니다. 우리가 이 도구를 사용할 때 가장 주의해야 할 개념은 바로 수명입니다. 여러분이 창문을 통해 마당의 꽃을 감상하고 있는데 누군가 갑자기 그 꽃을 뽑아버린다면 어떻게 될까요? 여러분의 창문은 이제 아무것도 없는 허공이나 혹은 전혀 다른 엉뚱한 대상을 가리키게 될 것입니다.

std::string_view가 바라보고 있던 원본 문자열이 메모리에서 해제되는 순간 std::string_view는 존재하지 않는 곳을 가리키는 댕글링 포인터가 되어버립니다. 이러한 위험성 때문에 std::string_view함수의 매개변수로 단기적으로 사용될 때 가장 안전하고 강력합니다. 만약 클래스의 멤버 변수로 이를 저장하고 싶다면 원본 데이터가 반드시 std::string_view보다 오래 살아남는다는 것을 보장해야만 합니다.

여러분의 이해도를 확인하는 퀴즈

배운 내용을 바탕으로 다음 상황을 생각해 보겠습니다. 아래 코드에서 std::string_view를 사용함으로써 얻는 가장 핵심적인 성능 이득은 무엇일까요?

void process(std::string_view sv) { /* 내부 로직 */ }

int main() {
    process("This is a very long string literal..."); 
}

질문: 위 사례에서 std::string_viewconst std::string&보다 우수한 이유는 무엇일까요?

  1. 문자열의 내용을 함수 내부에서 자유롭게 수정할 수 있기 때문에.
  2. 긴 문자열 리터럴을 인자로 넘길 때 임시 std::string 객체를 생성하지 않아 메모리 할당이 발생하지 않기 때문에.
  3. 문자열의 길이를 매번 O(n)으로 다시 계산할 필요가 없기 때문에.

정답은 2번입니다. std::string_view는 그저 "아 저기 메모리 주소 어디쯤에 40글자가 있구나"라고 기록만 할 뿐입니다. 이 사소해 보이는 차이가 대규모 시스템에서는 유의미한 성능의 향상을 만들어냅니다.

고수를 위한 고급 주의사항 - 널 종결 문자의 함정

마지막으로 숙련된 개발자도 종종 간과하는 함정이 하나 있습니다. 바로 널 종결 문자의 존재 여부입니다. 전통적인 C 스타일 함수들은 문자열의 끝에 반드시 \0이 있다고 가정하고 동작합니다. 하지만 std::string_view는 전체 문자열의 중간 부분만을 잘라서 보여줄 수 있습니다. 이 경우 잘린 끝부분에는 널 문자가 존재하지 않을 가능성이 매우 큽니다.

만약 std::string_view로 자른 부분 문자열의 주소 .data()printf("%s", ...)와 같은 C 함수에 그대로 넘긴다면 프로그램은 널 문자를 찾을 때까지 메모리의 엉뚱한 영역을 계속 읽어버리는 버퍼 오버런 사고를 일으킬 수 있습니다. 그러므로 std::string_view다룰 때는 항상 표준 스트림을 활용하거나 필요한 경우에만 명시적으로 std::string으로 변환하여 사용하는 지혜가 필요합니다.

읽기 전용으로 문자열을 처리하는 여러분의 모든 함수에 std::string_view를 도입해 보는 것은 어떨까요? 이 작은 창문 하나가 여러분의 코드를 훨씬 더 현대적이고 민첩하게 만들어 줄 것입니다.

공유하기