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
자주 묻는 질문 (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 | ‘문자열 처리’ 완벽 가이드 [실전 함수 총정리]
- C++ STL vector | ‘배열보다 편한’ 벡터 완벽 정리 [실전 예제]
- [C++ Copy Initialization: The = Form, explicit, and Copy](/en/blog/cpp-copy-initialization/
이 글에서 다루는 키워드 (관련 검색어)
C++, string, string_view, performance, strings, C++17 등으로 검색하시면 이 글이 도움이 됩니다.