Skip to content
C Codeloom
C++

C++ std::string_view Explained

Learn how std::string_view gives you cheap, non-owning views into strings — when to use it, how it speeds up code, and how to avoid dangling references.

·4 min read · By Codeloom
Intermediate 8 min read

What you'll learn

  • Understand what std::string_view is and is not
  • Use it as a function parameter to avoid copies
  • Recognize dangling string_view bugs
  • Compare with const std::string& and char*
  • Apply it in parsing and search code

Prerequisites

  • Strings basics — see /blog/cpp-strings-and-input

std::string_view, added in C++17, is a small, non-owning handle to a contiguous range of characters. It is a pointer plus a length. It does not allocate, copy, or own. Used well, it eliminates a huge amount of needless string copying. Used carelessly, it dangles.

What and Why

Before string_view, a function that read a string had to accept either const std::string& (which forced callers to convert their char* or substring into a std::string, sometimes copying), or accept multiple overloads, or take a const char* and size_t pair.

string_view unifies all of those. A const char*, a std::string, a substring, a string literal — all convert to string_view for free. The function reads characters and returns; no allocations involved.

Mental Model

Picture string_view as {const char* data; size_t size;}. That is essentially what it is. It does not null-terminate, it does not own, it does not extend lifetimes.

The corollary: the bytes it points to must outlive the string_view. If you store one as a member, you must guarantee the source string survives. If you build one from a temporary std::string, the view dies with the temporary.

Hands-on Example

A parsing helper that splits on a delimiter without copying.

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

std::vector<std::string_view> split(std::string_view s, char d) {
    std::vector<std::string_view> out;
    size_t start = 0;
    for (size_t i = 0; i <= s.size(); ++i) {
        if (i == s.size() || s[i] == d) {
            out.push_back(s.substr(start, i - start));
            start = i + 1;
        }
    }
    return out;
}

int main() {
    std::string line = "a,bb,ccc";
    for (auto part : split(line, ',')) {
        std::cout << part << "\n";
    }
}
std::string  "a,bb,ccc\0"
            ^ ^   ^
            | |   |
view1: ptr=0, len=1   ──> "a"
view2: ptr=2, len=2   ──> "bb"
view3: ptr=5, len=3   ──> "ccc"

No allocation. All views borrow from the original buffer.
string_view: a pointer plus length pointing into another buffer.

The split function never allocates a string. Each view points into the original line. If line is destroyed before the views are used, all views become dangling.

Common Pitfalls

The classic dangler:

std::string_view bad() {
    std::string s = build_something();
    return s; // s dies; the view points at freed memory
}

Just as bad: assigning a string_view from a temporary std::string and using it later. The temporary is destroyed at the end of the statement.

Another trap is forgetting that string_view is not null-terminated. Passing view.data() to a C function that expects a const char* like strlen may read past the end. Use std::string(view).c_str() when interoperating.

Comparing a string_view with == works element-by-element, which is what you want. But hashing into containers requires std::hash<std::string_view>, which you get only if you actually use string_view as the key type — using std::string as the key forces a conversion on lookup.

Practical Tips

Use std::string_view for read-only function parameters: void log(std::string_view msg). It accepts string literals, std::string, substrings, and char* without copies.

Do not return string_view from a function unless the underlying buffer demonstrably outlives the call (for example, a literal or a member of a long-lived object).

Avoid storing string_view as a class member unless your class clearly documents that the source must outlive it. For owned text, use std::string.

For C interop, prefer std::string when you need a guaranteed null terminator. For high-performance parsing, string_view is the right tool.

Wrap-up

std::string_view is the right type for “I just want to read these characters.” It removes a class of unnecessary copies and unifies many string-like inputs under one parameter type. The price is paying attention to lifetimes: a view is only valid while its source is alive. Master that one rule and string_view becomes a quiet workhorse in your codebase.