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.
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. 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.
Related articles
- C++ C++ Design Patterns Overview
Tour the most useful design patterns in modern C++ — singleton, factory, observer, strategy, RAII — and learn when each one earns its keep.
- C++ C++ Exceptions vs Error Codes
Compare exceptions and error codes in C++ for handling failures — performance, ergonomics, and when each style fits real codebases.
- C++ C++ Move Semantics and Rvalue References
How move semantics work in modern C++: rvalue references, std::move, perfect forwarding, and the rules that decide when your objects are copied versus moved.
- C++ C++ Unit Testing with GoogleTest
Set up GoogleTest in a CMake project and write clear unit tests with assertions, fixtures, and parameterized tests for confident C++ code.