第9.13章: string_view 与 cstring_view

English

概述

::fast_io::string_view 与 ::fast_io::cstring_view 都是对字符序列的非拥有视图。它们很轻(指针 + 大小),不需要复制。关键区别在于 cstring_view 保证以空字符结尾。

字符串视图还有许多其他操作(查找、修剪、比较等)。你可以先跳过这些细节,需要时再回到附录第9.13.1节。

1. basic_string_view

string_view 就是一个指针加一个大小。用它把字符串传给函数,不产生额外开销:


#include <fast_io_dsal/string_view.h>
#include <fast_io_dsal/string.h>
#include <fast_io.h>

void greet(::fast_io::string_view name) {
    ::fast_io::io::println("Hello, ", name, "!");
}

int main() {
    ::fast_io::string str{"Alice"};
    greet(str);           // From string (implicit conversion)
    greet("Bob");         // From string literal

    // Sub-view operations
    ::fast_io::string_view sv{"Hello, World!"};
    auto prefix = sv.subview_front(5zu);  // "Hello"
    auto suffix = sv.subview_back(6zu);   // "World!"
    auto middle = sv.subview(7zu, 5zu);   // "World"

    // Trim operations
    ::fast_io::string_view padded{"  hello  "};
    auto trimmed = padded.trim_subview_c_space(); // "hello"

    // Search
    auto pos = sv.find_character('W'); // 7
    bool has = sv.contains_character('o'); // true
}

字符类型变体

别名 字符类型
::fast_io::string_viewchar
::fast_io::wstring_viewwchar_t
::fast_io::u8string_viewchar8_t
::fast_io::u16string_viewchar16_t
::fast_io::u32string_viewchar32_t

2. cstring_view

::fast_io::cstring_view 是带空字符结尾保证的 string_view。它可以由 string(始终以空字符结尾)和字符串字面量构造。在与 C API 交互时使用它:


#include <fast_io_dsal/string.h>
#include <fast_io_dsal/cstring_view.h>
#include <fast_io.h>

void c_api_wrapper(::fast_io::cstring_view csv) {
    // Safe to pass .c_str() to C functions
    // ::some_c_function(csv.c_str());
}

int main() {
    ::fast_io::string str{"Hello"};
    ::fast_io::cstring_view csv{str}; // OK: string is always null-terminated
    c_api_wrapper(csv);
}

3. 悬空的 string_view

string_view 不拥有它的数据。它只是一个指针加一个大小 — 如果底层缓冲区被搬移或释放,这个视图就会悬空。

安全的部分:临时量不会造成悬空

fast_io::string_view 与 fast_io::span 只接受左值引用。这意味着你无法意外地从临时量创建视图 — 编译器会拒绝:


// This does NOT compile — string_view only binds to lvalues
::fast_io::string_view get_name() {
    ::fast_io::string temp{"hello"};
    return temp; // ERROR: cannot create string_view from a local that is about to die
}

因此其他语言里那种经典的悬空模式在这里不可能出现。编译器保护你不受指向已销毁对象的视图之害。

危险的部分:重新分配会使视图失效

危险:指向某个 string 的 string_view 会在该 string 重新分配时悬空。push_back()、insert_index()、replace_index()、append() 以及 operator+= 等操作都可能触发重新分配,把缓冲区搬到新的内存。已有的 string_view 仍然指向旧的(已释放的)缓冲区。

::fast_io::string s{"Hello, World!"};
::fast_io::string_view sv{s}; // sv points into s's buffer

// DANGER: this may reallocate s's internal buffer
s.append(" this is a long string that forces reallocation");

// sv is now dangling — it points to freed memory
// Reading sv is undefined behaviour

对指向 vector 或 deque 的 span 也是如此:任何使容器增长的操都可能使已有视图失效。

经验法则:不要跨任何可能修改底层容器的操作持有 string_view(或 span)。

要在测试期间发现悬空视图,可以使用 Wasm MemTag(WebAssembly 内存标记)。在启用内存标记的情况下编译到 Wasm 后,对已释放内存的访问会在运行期被捕获,从而使悬空视图的 bug 可被发现。

要点