C++17 표준 라이브러리의 std::string_view 소개

Twitter icon류광, 2017-04-07 18:04
조만간 공식화될 C++17에 추가된 std::string_view를 소개합니다.

std::string_view 클래스[1]는, 제안서(n3921)의 표현에 따르면 “문자열에 대한 비소유 참조(a non-owning reference to a string)”입니다. 좀 더 간단하게 말하면 문자열에 대한 “상수 참조” 또는 “읽기 전용 참조”라고 할 수도 있겠지만, 그렇게 말하면 이 클래스가 const std::string&를 돌려준다는 잘못된 인상을 줄 수 있습니다. 실제로, 원래는 클래스 이름이 string_ref이었지만 그런 오해를 피하자는 취지로 이름이 string_view로 바뀌었습니다.

(이하, 본문에서나 코드에서나 편의상 std::는 생략합니다.)

이 클래스의 주된 용도는 함수의 인수 또는 반환값에 쓰이는 const string&를 대신하는 것입니다. 예를 들어 다음과 같은 함수가 있다고 합시다.

void println(const string& str)
{
    cout << str << endl;
}

println은 상수 참조를 받으므로, 어떤 string 객체 sprintln를 호출하면 문자열이 복사되거나 문자열을 위한 메모리가 새로 할당되는 일이 없습니다. 그러나 다음 경우는 사정이 다릅니다.

println("Hello, world!");

이 호출에서 "Hello, world!"는 C 문자열 리터럴이고, 함수 호출의 맥락에서 이는 결국 const char*로 간주됩니다. printlnconst char*를 받지 않지만, 다행히(?) string에는 const char*를 받는 생성자가 있으므로 호출이 성공합니다. 문제는, 호출 과정에서 string의 생성자가 실행되어서 메모리 할당과 문자열 자료 복사가 발생한다는 점입니다. 궁극적인 목표인 cout << "Hello, world!" << endl;에 비하면 이는 불필요한 비용입니다.

사실 이 경우 cout에(그리고 문자열을 읽기 전용으로 다루는 여러 함수들에) 필요한 것은 문자열의 시작 위치와 문자열 길이뿐입니다(물론 문자의 형식도 필요하지만, 그건 컴파일 시점의 이야기이고요). string_view는 실제 문자열 자료를 소유하지 않고 그 두 정보만으로 하나의 문자열을 지칭합니다. 앞의 println을 다음과 같이 바꾸면,

void println(const string_view& str)
{
    cout << str << endl;
}

이제는 printlnstring 객체로 호출하든 const char*로 호출하든 복사나 메모리 할당이 일어나지 않습니다.

물론 void println(const char*) 중복적재 버전을 추가하거, 더 나아가서 template<typename T> println(T)로 일반화해서 이 문제를 해결할 수도 있겠지만, string_view를 사용할 수 있다면 굳이 그럴 필요는 없겠습니다.

앞의 예제에서 짐작하겠지만 string_viewoperator<<를 지원하며, =, ==, !=, <, >, <=, >=도 지원합니다. 사실 string_viewstd::string의 비수정 인터페이스를 거의 다 지원합니다. 주목할만한 차이점은, string_view에는 c_str() 멤버 함수가 없다는 점과 data()가 돌려주는 것이 널 종료 문자열이 아니라는 점입니다. (stringdata()도 예전에는 널 종료 문자열을 돌려주지 않았지만, C++11부터는 널 종료 문자열을 돌려주도록 바뀌었습니다.)

그리고 string_view만의 인터페이스도 추가되었는데요. 대표적인 것이 멤버 함수 remove_prefixremove_suffix입니다.

constexpr void remove_prefix(size_type n);
constexpr void remove_suffix(size_type n);

이름만 봐서는 문자열을 수정할 것 같지만, 이들은 그냥 string_view가 가진 유일한 정보인 문자열 시작 위치와 길이만 수정할 뿐입니다. remove_prefix는 문자열 시작 위치를 주어진 문자 개수만큼 문자열 끝쪽으로 옮기고, remove_suffix는 문자열의 길이를 주어진 개수만큼 줄입니다. 결과적으로 전자는 문자열의 앞부분, 즉 ‘접두사(prefix)’를 제거하는(remove) 효과를 내고, 후자는 문자열의 ‘접미사(suffix)’를 제거하는 효과를 냅니다.

마지막으로, C++17에는 string_view를 위한 문자열 리터럴도 추가되었습니다. "Hello, world!"sv처럼 끝에 접미사 sv가 붙은 문자열 리터럴은 상수 string_view 객체가 됩니다. (이 접미사를 사용하려면 using namespace std::literalsusing namespace std::string_view_literals가 필요합니다.)


  1. 참고로, 실질적인 기능을 제공하는 것은 std::basic_string_view라는 템플릿이고, std::string_view는 미리 정의된 std::basic_string_view<char>의 별칭입니다. std::basic_string을 설명할 때 흔히 std::string을 예로 드는 것과 같은 맥락에서, 이 글에서도 std::string_view를 예로 사용합니다. 

태그: 프로그래밍 C++ C++17 표준 라이브러리

comments powered by Disqus