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
| Property | std::string | std::string_view |
|---|---|---|
| Ownership | Owns the buffer | Non-owning |
| Allocation | Heap (unless SSO) | None |
| Construction from literal | O(n) copy | O(1) pointer+length |
| Mutability | Mutable | Read-only |
| Null-terminated | Yes (.c_str()) | Not guaranteed |
| Size | ~24-32 bytes (+ heap) | 16 bytes (2 words) |
| substr() | New allocation | O(1) — returns view |
| Safe to store | Always | Only if source outlives |
| Available since | C++98 | C++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_viewfor read-only parameters — acceptsstd::string, string literals, andconst char*without copying string_view::substris O(1) — just adjusts pointer and length, no allocation; ideal for parsers and tokenizers- Never return
string_viewto a local — it dangles immediately when the local is destroyed - Mutation invalidates views — don’t keep a
string_viewinto astd::stringthat might be modified or grow string_viewis not null-terminated — don’t pass.data()to C APIs that expect null termination; usestd::string(sv).c_str()- Storing
string_viewin a class member requires careful lifetime ownership — often better to own astd::stringand returnstring_viewfrom 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.
Related Articles (Internal Links)
Other articles related to this topic.
- C++ string | ‘문자열 처리’ 완벽 가이드 [실전 함수 총정리]
- C++ STL vector | ‘배열보다 편한’ 벡터 완벽 정리 [실전 예제]
- C++ Copy Initialization: The = Form, explicit, and Copy
Keywords Covered in This Article (Related Search Terms)
This article covers C++, string, string_view, performance, strings, C++17.