std::string_view Lifetime Hazards in C++
The std::string_view utility introduced in C++17
provides a lightweight, non-owning view of a character sequence,
significantly reducing unnecessary memory allocations. However, because
it does not manage the underlying data, developers face severe lifetime
hazards—most commonly dangling references caused by binding a view to a
temporary string. This article breaks down the mechanics of
std::string_view, illustrates how returning one bound to a
temporary results in undefined behavior, and provides concrete rules to
avoid these pitfalls.
What Is a std::string_view Lifetime Hazard?
At its core, std::string_view is implemented as a
pointer to a character buffer and a size:
class string_view {
const char* data_;
std::size_t size_;
};Because it is purely an observer, it has no ownership semantics. It
does not allocate memory on creation, nor does it free memory upon
destruction. Consequently, a lifetime hazard occurs
whenever the lifetime of the std::string_view exceeds the
lifetime of the character array it references. Once the underlying
buffer is deallocated or goes out of scope, the
std::string_view becomes a dangling pointer. Accessing it
triggers undefined behavior (UB).
How Returning from a Temporary Causes Dangling References
A common source of bugs is returning a std::string_view
initialized from a temporary or local std::string.
1. Returning a Local
std::string
Consider the following function:
std::string_view get_greeting() {
std::string greeting = "Hello, World!";
return greeting; // Hazard: Implicit conversion to std::string_view
}Here, greeting is a local variable stored on the stack
with automatic storage duration. When the return statement
executes:
greetingis implicitly converted to astd::string_view, pointing to the internal buffer ofgreeting.- The function ends, and
greetinggoes out of scope, destroying its internal buffer. - The returned
std::string_viewnow points to invalid, deallocated stack or heap memory.
Calling code that reads the returned view will encounter undefined behavior, such as garbage output or memory access violations:
std::string_view sv = get_greeting();
std::cout << sv; // Undefined Behavior: sv points to destroyed memory2. Returning a Temporary from String Manipulation
Lifetime hazards also emerge when returning string views from
expressions that generate temporary std::string
objects:
std::string_view get_full_name(const std::string& first, const std::string& last) {
return first + " " + last; // Hazard: operator+ returns a temporary std::string
}The expression first + " " + last generates a temporary
std::string. The function converts this temporary into a
std::string_view and returns it. Because temporaries are
destroyed at the end of the full expression in which they are created,
the temporary is destroyed before the caller can even inspect the
returned view.
3. Immediate Binding to a Temporary
This hazard does not only occur across function boundaries; it can occur in a single line of code:
std::string get_data();
// Hazard: The temporary returned by get_data() is destroyed
// at the semicolon, leaving sv dangling.
std::string_view sv = get_data();
std::cout << sv; // Undefined BehaviorWhile const std::string& can extend the lifetime of
a temporary to the lifetime of the reference,
std::string_view does not extend the lifetime of a
temporary std::string.
Rules for Safe Usage
To prevent dangling references with std::string_view,
apply the following guidelines:
- Use
std::string_viewfor input parameters: It is primarily designed to replaceconst std::string&in function arguments, allowing functions to accept string literals,std::string, and raw arrays without heap allocation. - Return
std::stringby value when creating data: If a function creates, combines, or transforms text, returnstd::stringby value. Rely on Return Value Optimization (RVO) and move semantics for performance. - Only return
std::string_viewif the referenced data outlives the caller: It is safe to return astd::string_viewreferencing a string literal (which has static storage duration) or referencing an input parameter that is guaranteed to outlive the function call. - Enable compiler diagnostics: Modern compilers
provide warnings for these scenarios (such as Clang's
-Wdangling-gslor GCC's-Wreturn-local-addr). Treat these warnings as errors.