c++ string 的allocate和cow

string 可以说是c++ stl 中最常用的容器了,首先弄明白 cpp 中的string 到底是在哪里存储的,下面的代码我们重载了全局的new和delete

#include <string>
#include <iostream>
#include <cstdlib>
#include <vector>
using namespace std;

// 重载全局 new
void* operator new(std::size_t size) {
    cout << "[Allocating " << size << " bytes]\n";
    return malloc(size);
}

// 重载全局的 delete
// void operator delete(void* ptr) noexcept {
//     cout << "[Deallocating]\n";
//     return free(ptr);
// }

int main(int argc, char *argv[]) {
    for (int i = 1; i <= 17; ++i) {
        cout << i << ":" << string(i, '*') << "\n";
    }
    string(16, '*').append("*").append("**");
    cout << "---------------------------\n";

    string s1(16, '#');
    s1.append("#").append("##");
    string s2(s1);

    return 0;
}

运行结果如下:

1:*
2:**
3:***
4:****
5:*****
6:******
7:*******
8:********
9:*********
10:**********
11:***********
12:************
13:*************
14:**************
15:***************
16:[Allocating 17 bytes]
****************
17:[Allocating 18 bytes]
*****************
[Allocating 17 bytes]
[Allocating 33 bytes]
---------------------------
[Allocating 17 bytes]
[Allocating 33 bytes]
[Allocating 20 bytes]  // no copy on write

通过上面的代码发现有下面2点:

  1. 发现在size小于16的短小string并没有调用new,而是直接分配在栈上的
  2. 使用s1来初始化s2的时候直接分配了内存,并没有使用cow, cpp 高版本取消了cow

string_view

string_view 是 C++17 引入的一个重要类,它提供了一个轻量级的、非拥有的字符串视图。它本质上是一个指向字符序列的指针加上长度信息。

基本概念

string_view 不拥有字符串数据,它只是引用已存在的字符串数据。这使得它非常轻量,可以高效地传递和操作字符串,而无需复制数据。

主要优点

  1. 性能: 无需复制字符串数据,避免了内存分配
  2. 安全性: 有边界检查,避免缓冲区溢出
  3. 灵活性: 可以安全地引用各种字符串类型(C 风格字符串、std::string 等)

基本用法示例

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

int main() {
    // 从 C 风格字符串创建
    const char* cstr = "Hello World";
    std::string_view sv1(cstr);
    
    // 从 std::string 创建
    std::string str = "Hello World";
    std::string_view sv2(str);
    
    // 直接字面量
    std::string_view sv3("Hello World");
    
    // 指定长度
    std::string_view sv4(cstr, 5); // "Hello"
    
    std::cout << sv1 << std::endl; // 输出: Hello World
    std::cout << sv4 << std::endl; // 输出: Hello
    
    return 0;
}

常用操作

#include <iostream>
#include <string_view>

int main() {
    std::string_view sv = "Hello, World!";
    
    // 获取长度
    std::cout << "Length: " << sv.length() << std::endl;
    
    // 访问字符
    std::cout << "First char: " << sv[0] << std::endl;
    std::cout << "Last char: " << sv.at(sv.length()-1) << std::endl;
    
    // 子串操作
    std::string_view sub = sv.substr(0, 5); // "Hello"
    std::cout << "Substring: " << sub << std::endl;
    
    // 查找
    size_t pos = sv.find("World"); // 7
    if (pos != std::string_view::npos) {
        std::cout << "Found 'World' at position: " << pos << std::endl;
    }
    
    // 比较
    std::string_view sv2 = "Hello";
    if (sv.substr(0, 5) == sv2) {
        std::cout << "First 5 chars match 'Hello'" << std::endl;
    }
    
    return 0;
}

与 std::string 的区别

特性std::stringstd::string_view
数据拥有权
内存分配可能需要不需要
大小sizeof(std::string)sizeof(指针+长度)
修改能力可修改只读
性能较低较高

使用注意事项

  1. 生命周期: 必须确保被引用的字符串数据在 string_view 使用期间保持有效
  2. 只读: string_view 不允许修改底层数据
  3. 非空终止: string_view 不保证以空字符结尾

实际应用场景

// 函数参数:避免不必要的字符串复制
void process_text(std::string_view text) {
    std::cout << "Processing: " << text << " (length: " << text.length() << ")" << std::endl;
}

int main() {
    // 可以传入不同类型的字符串而无需转换
    process_text("C-style string");           // 字面量
    process_text(std::string("std::string")); // std::string
    
    const char* ptr = "Pointer to string";
    process_text(std::string_view(ptr, 6));   // 自定义视图
    
    return 0;
}

使用 string_view 可以显著提高程序性能,特别是在需要频繁传递字符串参数但不需要修改字符串的场景中。