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:

  1. greeting is implicitly converted to a std::string_view, pointing to the internal buffer of greeting.
  2. The function ends, and greeting goes out of scope, destroying its internal buffer.
  3. The returned std::string_view now 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 memory

2. 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 Behavior

While 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_view for input parameters: It is primarily designed to replace const std::string& in function arguments, allowing functions to accept string literals, std::string, and raw arrays without heap allocation.
  • Return std::string by value when creating data: If a function creates, combines, or transforms text, return std::string by value. Rely on Return Value Optimization (RVO) and move semantics for performance.
  • Only return std::string_view if the referenced data outlives the caller: It is safe to return a std::string_view referencing 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-gsl or GCC's -Wreturn-local-addr). Treat these warnings as errors.