Skip to content

Latest commit

 

History

History
236 lines (177 loc) · 11 KB

stdstring_view.md

File metadata and controls

236 lines (177 loc) · 11 KB

std::string_view


Executive Abstract

std::string_view (std::basic_string_view<CharT>) is a lightweight, non-owning reference abstraction in <string_view> that encapsulates a pointer to a contiguous sequence of characters and a character length count. Standardized in C++17 and expanded in C++20 (starts_with, ends_with) and C++23 (contains), std::string_view unifies string parameters across C-style raw character arrays (const char*), owning string containers (std::string), and compile-time string constants ("literal"sv). It eliminates temporary dynamic heap allocations, enables $\mathcal{O}(1)$ zero-copy substring slicing (.substr()), and serves as the standard read-only parameter type for modern C++ string APIs.


Motivation & Historical Context

Pre-C++17 Deficiencies: The String Parameter Dilemma

Prior to C++17, defining a function that accepted read-only string parameters forced developers into an architectural dilemma:

// Pre-C++17 Option 1: Const Reference to std::string
void process_log(const std::string& msg);
process_log("System Warning"); // DANGEROUS: Forces dynamic heap allocation for temporary std::string!

// Pre-C++17 Option 2: Raw C-Style Character Pointer
void process_log(const char* msg);
process_log("System Warning"); // FLAGGED: Loses length metadata, forcing O(N) strlen() scans!

Deficiencies of Pre-C++17 Read-Only String Parameters:

  1. Forced Temporary Heap Allocations: Passing a C-style string literal ("hello") to a function taking const std::string& constructed a temporary std::string object, executing a dynamic heap allocation (malloc/free) merely to read a constant array of bytes.
  2. Pervasive $\mathcal{O}(N)$ strlen Re-scans: Passing raw const char* pointers discarded length metadata, forcing every string function to re-scan character buffers sequentially to find the null-terminator.
  3. Expensive Substring Slicing: Calling std::string::substr() allocated a brand-new std::string on the heap and copied the character bytes, making string parsers (JSON, XML, HTTP headers) allocation-heavy and slow.

Solution: C++17 introduced std::string_view, storing a pointer and an integer size in a 16-byte stack frame (sizeof(string_view) == 2 * sizeof(void*)), achieving zero-copy string borrowing.


Technical Mechanics & Memory Representation

1. Memory Footprint & Layout

std::string_view consists of exactly two scalar data members:

                       std::string_view Memory Representation (16 Bytes)
 ┌────────────────────────────────────────┬────────────────────────────────────────┐
 │ const char* m_data                     │ std::size_t m_size                     │
 ├────────────────────────────────────────┼────────────────────────────────────────┤
 │ 8-Byte Pointer to Character Sequence   │ 8-Byte Character Count                 │
 └──────────────────┬─────────────────────┴────────────────────────────────────────┘
                    │
                    ▼
            [ "a" | "v" | "a" | "n" | "t" | "-" | "g" | "a" | "r" | "d" | "e" ]
            (Borrows existing memory; ZERO dynamic heap allocation!)
  • Pass-By-Value Efficiency: Because std::string_view is small (16 bytes on 64-bit platforms), it fits directly into two 64-bit CPU registers. Always pass std::string_view by value (void func(std::string_view sv)), rather than by reference (const std::string_view&).

2. Operational Complexity & Zero-Copy Substrings

  • Construction / Copying / Assignment: $\mathcal{O}(1)$ constant time.
  • Substring Slicing (.substr(pos, count)): $\mathcal{O}(1)$ constant time. Slicing adjusts m_data pointer offset and m_size integer without allocating memory or copying characters!

3. CRITICAL INVARIANT: The Null-Terminator Non-Guarantee

Null-Terminator Warning: Unlike C-strings (const char*) or std::string::c_str(), a std::string_view is NOT guaranteed to be null-terminated (\0)!

std::string text = "Hello World";
std::string_view slice = std::string_view(text).substr(0, 5); // View over "Hello"

// DANGER: 'slice' is NOT null-terminated!
// slice.data()[5] points to ' ' (space), NOT '\0'!
// Passing slice.data() to C-APIs expecting null-terminated strings (printf, fopen)
// causes OUT-OF-BOUNDS MEMORY CORRUPTION!

Evolution Across Standards (C++17 to C++26/C++29)

timeline
    title std::string_view Evolution
    C++17 : Introduced std::string_view in <string_view>
          : Added "text"sv literal suffix in std::string_view_literals
          : O(1) zero-copy substr() slicing
    C++20 : Introduced std::u8string_view for UTF-8 char8_t
          : Added .starts_with() and .ends_with() member methods
          : Full constexpr conversion for all string_view member operations
    C++23 : Added .contains() method for substring searching
    C++26 : Native format and print formatting for string_view
Loading
                             │
                             ▼

┌──────────────────────────────────────────────────────────────────┐ │ C++23 / C++26 │ ├──────────────────────────────────────────────────────────────────┤ │ • Added .contains() substring existence check in C++23 │ │ • Range / View construction overload (std::from_range) │ │ • Native formatting in std::format and std::print │ └──────────────────────────────────────────────────────────────────┘


---

## Comprehensive Code Examples

### Example 1: Zero-Allocation String Parameter Ingestion & $\mathcal{O}(1)$ Substring Slicing
Demonstrates `std::string_view` accepting C-strings, `std::string`, and string literals seamlessly.

```cpp
#include <iostream>
#include <string>
#include <string_view>

// Parameter accepts any string representation WITHOUT temporary heap allocations!
void parse_protocol_header(std::string_view header) {
    std::cout << "Full Header: " << header << " | Length: " << header.size() << "\n";

    // O(1) Zero-Copy Substring Slicing
    std::string_view prefix = header.substr(0, 4); // Views "HTTP"
    std::cout << "Protocol Prefix: " << prefix << " (Zero-copy slice!)\n";
}

int main() {
    using namespace std::string_view_literals;

    // 1. Pass string literal directly (NO std::string allocation!)
    parse_protocol_header("HTTP/1.1 200 OK");

    // 2. Pass owning std::string
    std::string dynamic_header = "HTTP/2.0 404 NOT_FOUND";
    parse_protocol_header(dynamic_header);

    // 3. Pass C++17 string_view literal ("..."sv)
    parse_protocol_header("HTTP/3.0 500 ERROR"sv);

    return 0;
}

Example 2: Modern C++20/C++23 String Queries (starts_with, ends_with, contains)

Demonstrates expressive string inspections added in C++20 and C++23.

#include <iostream>
#include <string_view>

void inspect_url(std::string_view url) {
    // C++20 starts_with and ends_with
    if (url.starts_with("https://")) {
        std::cout << "Secure HTTPS Connection Detected.\n";
    }

    if (url.ends_with(".png") || url.ends_with(".jpg")) {
        std::cout << "Image Resource URL Detected.\n";
    }

    // C++23 contains method
#if defined(__cpp_lib_string_view_contains) && __cpp_lib_string_view_contains >= 202011L
    if (url.contains("api/v1")) {
        std::cout << "API Version 1 Endpoint Detected.\n";
    }
#endif
}

int main() {
    inspect_url("https://example.com/api/v1/avatar.png");
    return 0;
}

Example 3: The Dangling string_view Lifetime Crash

Demonstrates a critical bug: creating a string_view referencing a temporary std::string that goes out of scope.

#include <iostream>
#include <string>
#include <string_view>

std::string_view create_dangling_view() {
    std::string temporary_str = "Temporary Data Buffer";

    // DANGEROUS BUG: Returning a string_view referencing local variable 'temporary_str'!
    return temporary_str;
} // 'temporary_str' is DESTROYED HERE! Returned string_view points to FREED MEMORY!

int main() {
    std::string_view dangling = create_dangling_view();

    // DANGEROUS UNDEFINED BEHAVIOR: Dereferencing dangling pointer!
    // std::cout << dangling << "\n"; // CRASH / Memory Corruption!

    std::cout << "Dangling string_view hazard demonstrated.\n";
    return 0;
}

Engineering Best Practices & Common Pitfalls

  1. Pass std::string_view By Value:

    • Write void process(std::string_view sv), not void process(const std::string_view& sv). Because std::string_view is 16 bytes, passing by value allows compilers to pass it inside CPU registers.
  2. CRITICAL: Lifetime Invariant Management:

    • std::string_view is a non-owning view. It does not manage the lifespan of the underlying character array.
    • Rule: Never store a std::string_view in a long-lived class data member or return a std::string_view from a function unless you guarantee that the referenced string storage outlives the view.
  3. Interoperability with C APIs:

    • Never pass sv.data() directly to C functions expecting null-terminated C-strings (fopen, printf, strlen) unless you know the view covers an entire null-terminated buffer.
    • Fix: If a null-terminated string is required, construct an explicit std::string:
      std::string null_terminated_str(sv);
      c_api_function(null_terminated_str.c_str());

Relationships

  • Related to:
    • std::string — Owning string container.
    • std::span (C++20) — Contiguous non-owning view over arbitrary element arrays (std::span<T>).
    • std::format / std::print (C++20/C++23) — Native formatting support for string views.
    • Raw String Literals — Unescaped string input for string views.
  • Superseded by: None.
  • Supersedes: const std::string& parameters for read-only string APIs and raw const char* pointer passing.

Back to C++17 section.