본문으로 건너뛰기 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

Frequently Asked Questions (FAQ)

Q. When would I use this in practice?

A. std::string vs std::string_view: avoid copies in read-only APIs, allocation costs, lifetime rules, substring.

Q. What should I read before this?

A. Follow the previous article or related articles links at the bottom of each post to learn in sequence. See the C++ series index for the full picture.

Q. Where can I study this more deeply?

A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.


Other articles related to this topic.


Keywords Covered in This Article (Related Search Terms)

This article covers C++, string, string_view, performance, strings, C++17.