본문으로 건너뛰기
Previous
Next
C++ string vs string_view: Fast, Non-Owning String Handling

C++ string vs string_view: Fast, Non-Owning String Handling

C++ string vs string_view: Fast, Non-Owning String Handling

이 글의 핵심

std::string vs std::string_view: avoid copies in read-only APIs, allocation costs, lifetime rules, substring performance, and null-termination caveats.

The Problem: Unnecessary String Copies

A common C++ performance problem is passing strings by value when the function only needs to read them:

// Takes string by value — always copies (or moves, but still an allocation)
void logMessage(std::string msg) {
    std::cout << "[LOG] " << msg << '\n';
}

// At the call site:
logMessage("Connection established");  // allocates a std::string, copies chars
logMessage(userInput);                  // copies userInput into a new std::string

std::string_view (C++17) solves this. It’s a lightweight, non-owning reference to a character sequence — no allocation, no copy:

#include <string_view>

void logMessage(std::string_view msg) {
    std::cout << "[LOG] " << msg << '\n';
}

logMessage("Connection established");  // no allocation — points to literal
logMessage(userInput);                  // no copy — points into userInput's buffer

What string_view Actually Is

string_view is essentially a struct with two fields:

struct string_view {
    const char* ptr;  // pointer to character data (not necessarily null-terminated)
    size_t len;       // number of characters
};

Creating one from any string-like source is O(1):

#include <string>
#include <string_view>

const char* literal = "hello";
std::string stdString = "world";

std::string_view sv1 = literal;    // points to literal, len=5
std::string_view sv2 = stdString;  // points to stdString's buffer
std::string_view sv3{"hello", 3};  // points to "hel" — first 3 chars only
std::string_view sv4 = sv1.substr(1, 3);  // "ell" — no allocation

Comparison Table

Propertystd::stringstd::string_view
OwnershipOwns the bufferNon-owning
AllocationHeap (unless SSO)None
Construction from literalO(n) copyO(1) pointer+length
MutabilityMutableRead-only
Null-terminatedYes (.c_str())Not guaranteed
Size~24-32 bytes (+ heap)16 bytes (2 words)
substr()New allocationO(1) — returns view
Safe to storeAlwaysOnly if source outlives
Available sinceC++98C++17

Where string_view Wins: Substring Performance

std::string::substr always allocates a new string. std::string_view::substr just adjusts the pointer and length — no allocation:

#include <string>
#include <string_view>
#include <iostream>

std::string text = "The quick brown fox jumps over the lazy dog";

// string::substr — allocates a new string
std::string sub1 = text.substr(4, 5);   // "quick" — heap allocation

// string_view::substr — zero allocation
std::string_view view = text;
std::string_view sub2 = view.substr(4, 5);  // "quick" — just pointer+length
std::cout << sub2 << '\n';  // works fine for read-only use

For parsing-heavy code that slices strings frequently (CSV parsers, HTTP header parsers, tokenizers), this difference can be significant.

Tokenizing with string_view

#include <string_view>
#include <vector>
#include <iostream>

std::vector<std::string_view> split(std::string_view s, char delim) {
    std::vector<std::string_view> parts;
    size_t start = 0;
    size_t pos;
    while ((pos = s.find(delim, start)) != std::string_view::npos) {
        parts.push_back(s.substr(start, pos - start));
        start = pos + 1;
    }
    parts.push_back(s.substr(start));
    return parts;
}

int main() {
    std::string csv = "alice,bob,charlie,dave";
    // All the string_views point into csv — zero copies
    for (auto token : split(csv, ',')) {
        std::cout << token << '\n';
    }
    // alice / bob / charlie / dave
}

Parameter Guidelines

The choice of parameter type depends on what the function does with the string:

// Read-only: use string_view — accepts string, literal, string_view, char* all without copy
void print(std::string_view s);
bool contains(std::string_view haystack, std::string_view needle);
size_t count(std::string_view s, char c);

// Mutate in place: must use string reference
void toLower(std::string& s);
void trimInPlace(std::string& s);

// Store ownership (sink): take by value (caller moves in)
class Logger {
    std::string prefix_;
public:
    explicit Logger(std::string prefix) : prefix_(std::move(prefix)) {}
};

// Need to pass to C API that requires null-termination
void callCLibrary(const std::string& s) {
    c_function(s.c_str());  // std::string guarantees null-termination
}

Quick rule: if the function body only reads the string, use string_view. If it stores a copy, takes ownership, or modifies, use std::string.


Lifetime Pitfalls

string_view does not own its data. If the underlying data is destroyed, the view becomes dangling — just like a dangling reference.

Dangling View from Temporary

#include <string>
#include <string_view>

std::string_view danger() {
    std::string local = "hello";
    return local;  // WRONG — local destroyed, view dangles
}

// Even worse — the bug is hidden
std::string_view sv = std::string("hello");  // temporary destroyed immediately
// sv is dangling — undefined behavior

Dangling View from String Mutation

std::string may reallocate its buffer when it grows. Any string_view into a std::string before reallocation becomes invalid:

std::string s = "hello";
std::string_view sv = s;

s += " world";  // may reallocate — sv is now potentially dangling!
std::cout << sv << '\n';  // undefined behavior if reallocation happened

Safe Patterns

// Safe: string_view parameter — caller guarantees the string outlives the call
void process(std::string_view sv) {
    // sv is valid during this function
}

// Safe: string_view from a string_literal (static lifetime)
constexpr std::string_view greeting = "Hello, World!";

// Safe: storing in a class that owns the string
class Parser {
    std::string data_;  // owns the buffer
    std::string_view current_;  // valid as long as data_ is not modified

public:
    void load(std::string input) {
        data_ = std::move(input);
        current_ = data_;  // safe — points into data_, which we own
    }
};

Null-Termination Caveat

std::string guarantees null-termination — s.c_str() is always safe to pass to C APIs. string_view makes no such guarantee:

#include <cstdio>
#include <string_view>

void wrongWay(std::string_view sv) {
    // WRONG — sv.data() is not null-terminated in general
    printf("%s\n", sv.data());
}

void rightWay(std::string_view sv) {
    // If you need null termination, convert first
    std::string s(sv);
    printf("%s\n", s.c_str());

    // Or use the length-aware C functions
    fwrite(sv.data(), 1, sv.size(), stdout);
    putchar('\n');
}

String literals happen to be null-terminated, so string_view{"hello"}.data() would work with printf. But a string_view created from sv.substr(1, 3) is pointing into the middle of a buffer and is not null-terminated. Don’t rely on it.


string_view as a Class Member

Storing string_view as a member is risky unless you carefully control the lifetime:

// RISKY — who owns the string the view points to?
class Config {
    std::string_view host_;  // dangerous if lifetime not guaranteed
};

// SAFE — own the string, expose view
class Config {
    std::string host_;
public:
    explicit Config(std::string host) : host_(std::move(host)) {}
    std::string_view host() const { return host_; }  // returns view — safe
};

C++20 Additions

C++20 adds a few useful string_view methods:

std::string_view s = "Hello, World!";

// starts_with and ends_with (C++20)
s.starts_with("Hello");   // true
s.ends_with("World!");    // true

// contains (C++23)
// s.contains("World");   // true — C++23 only

Key Takeaways

  • Use string_view for read-only parameters — accepts std::string, string literals, and const char* without copying
  • string_view::substr is O(1) — just adjusts pointer and length, no allocation; ideal for parsers and tokenizers
  • Never return string_view to a local — it dangles immediately when the local is destroyed
  • Mutation invalidates views — don’t keep a string_view into a std::string that might be modified or grow
  • string_view is not null-terminated — don’t pass .data() to C APIs that expect null termination; use std::string(sv).c_str()
  • Storing string_view in a class member requires careful lifetime ownership — often better to own a std::string and return string_view from an accessor

자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. std::string vs std::string_view: avoid copies in read-only APIs, allocation costs, lifetime rules, substring performance… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.


이 글에서 다루는 키워드 (관련 검색어)

C++, string, string_view, performance, strings, C++17 등으로 검색하시면 이 글이 도움이 됩니다.