C++编程:使用std::string仿写c++17的std::string_view

0. 引言

在性能敏感的应用中(如网络库、系统编程等),频繁的std::strring字符串拷贝会带来性能开销,而C++17 字符串string_view可以减少拷贝。
因为我们项目规定只能使用C++14,在之前的C++编程:使用 mmap 和 std::string_view 优化配置文件的读取与解析这篇文章中实际使用的是自己实现的string_view。

本文将参考开源库muduo的StringPiece.h 实现基于 C++11 实现的的 StringView 类,以避免不必要的内存分配和字符串拷贝。

适用场景

  • 需要高效处理大文本(如日志分析)。
  • 频繁传递字符串,但无需修改其内容。
  • 需要替代 C++17 的std::string_view的场景。

1. 完整代码

#include <algorithm>  // for std::min, std::max
#include <cstring>    // for std::strlen, std::memcmp
#include <iostream>
#include <stdexcept>
#include <string>  // for std::string

class StringView {
    
    
 private:
  const char* data_ = nullptr;  // 指向外部字符串
  std::size_t size_ = 0;        // 字符串长度

  static constexpr std::size_t npos = static_cast<std::size_t>(-1);  // 定义 npos

 public:
  // 默认构造函数
  StringView() noexcept = default;

  // 从 C 风格字符串构造
  explicit StringView(const char* str) : data_(str), size_(str ? std::strlen(str) : 0) {
    
    
  }

  // 从 C 风格字符串构造,并指定长度
  StringView(const char* str, std::size_t len) : data_(str), size_(str ? len : 0) {
    
    
  }

  // 从 std::string 构造
  explicit StringView(const std::string& str) noexcept : data_(str.data()), size_(str.size()) {
    
    
  }

  // 获取字符串长度
  std::size_t size() const noexcept {
    
    
    return size_;
  }

  // 检查是否为空
  bool empty() const noexcept {
    
    
    return size_ == 0;
  }

  // 获取字符串数据
  const char* data() const noexcept {
    
    
    return data_;
  }

  // 获取字符(带异常安全性检查)
  const char& operator[](std::size_t index) const {
    
    
    if (!data_ || index >= size_) {
    
    
      throw std::out_of_range("Index out of range");
    }
    return data_[index];
  }

  // 获取子视图(改进异常处理和边界检查)
  StringView substr(std::size_t pos, std::size_t len = npos) const {
    
    
    if (pos > size_) {
    
    
      throw std::out_of_range("Position out of range");
    }
    len = std::min(len, size_ - pos);  // 修正 len
    return StringView(data_ + pos, len);
  }

  // 比较两个 StringView
  int compare(const StringView& other) const noexcept {
    
    
    std::size_t len = std::min(size_, other.size_);
    int result = std::memcmp(data_, other.data_, len);
    return result != 0 ? result : static_cast<int>(size_ - other.size_);
  }

  // 检查是否以指定前缀开头
  bool starts_with(const StringView& prefix) const noexcept {
    
    
    return size_ >= prefix.size_ && std::memcmp(data_, prefix.data_, prefix.size_) == 0;
  }

  // 检查是否以指定后缀结尾
  bool ends_with(const StringView& suffix) const noexcept {
    
    
    return size_ >= suffix.size_ && std::memcmp(data_ + size_ - suffix.size_, suffix.data_, suffix.size_) == 0;
  }

  // 检查是否包含指定子字符串
  bool contains(const StringView& sub) const noexcept {
    
    
    if (sub.size_ > size_) return false;
    for (std::size_t i = 0; i <= size_ - sub.size_; ++i) {
    
    
      if (std::memcmp(data_ + i, sub.data_, sub.size_) == 0) {
    
    
        return true;
      }
    }
    return false;
  }

  // 输出字符串(增加空指针检查)
  friend std::ostream& operator<<(std::ostream& os, const StringView& sv) {
    
    
    if (sv.data_ && sv.size_ > 0) {
    
    
      os.write(sv.data_, sv.size_);
    }
    return os;
  }

  // 操作符重载:==, !=, <, <=, >, >=
  bool operator==(const StringView& other) const noexcept {
    
    
    return size_ == other.size_ && compare(other) == 0;
  }

  bool operator!=(const StringView& other) const noexcept {
    
    
    return !(*this == other);
  }

  bool operator<(const StringView& other) const noexcept {
    
    
    return compare(other) < 0;
  }

  bool operator<=(const StringView& other) const noexcept {
    
    
    return compare(other) <= 0;
  }

  bool operator>(const StringView& other) const noexcept {
    
    
    return compare(other) > 0;
  }

  bool operator>=(const StringView& other) const noexcept {
    
    
    return compare(other) >= 0;
  }
};

2. 实现原理

以下是主要实现点的解释:

  • 数据成员:

    • data_:指向外部字符串的指针。
    • size_:表示字符串的长度,避免使用 std::strlen 重复计算。
  • 构造函数:

    • 支持从 C 风格字符串(const char*)或指定长度构造。
    • nullptr 字符串处理为长度 0,确保安全。
  • 基本操作:

    • 提供 substr 创建子字符串视图,并通过边界检查避免越界。
    • starts_withends_withcontains 用于高效地检查前缀、后缀和子串。
  • 性能优化:

    • 比较操作基于 std::memcmp,支持按字节快速比较子串。
    • operator<< 直接输出字符串内容,兼容标准输出流。
  • 安全性:

    • 处理了空指针、越界访问等潜在问题,符合 C++ 标准的异常安全性。

3. 测试程序

以下是测试 StringView 功能的示例程序,涵盖构造、访问、修改和比较操作。

int main() {
    
    
  const char* text = "Hello, world!";
  StringView sv1(text);     // 整个字符串
  StringView sv2(text, 5);  // 前 5 个字符
  std::string str = "Hello, OpenAI!";
  StringView sv3(str);  // 从 std::string 构造

  std::cout << "sv1: " << sv1 << ", size: " << sv1.size() << "\n";
  std::cout << "sv2: " << sv2 << ", size: " << sv2.size() << "\n";
  std::cout << "sv3: " << sv3 << ", size: " << sv3.size() << "\n";

  std::cout << "sv1 starts with 'Hello': " << sv1.starts_with(StringView("Hello")) << "\n";
  std::cout << "sv1 ends with 'world!': " << sv1.ends_with(StringView("world!")) << "\n";
  std::cout << "sv1 contains 'world': " << sv1.contains(StringView("world")) << "\n";

  try {
    
    
    auto sv4 = sv1.substr(7, 5);
    std::cout << "sv4 (substr): " << sv4 << "\n";
  } catch (const std::out_of_range& e) {
    
    
    std::cout << "Error: " << e.what() << "\n";
  }

  std::cout << "Comparison (sv1 vs sv2): " << (sv1 == sv2) << "\n";

  return 0;
}

4. 输出结果

运行上述测试程序后,输出类似以下内容:

sv1: Hello, world!, size: 13
sv2: Hello, size: 5
sv3: Hello, OpenAI!, size: 14
sv1 starts with 'Hello': 1
sv1 ends with 'world!': 1
sv1 contains 'world': 1
sv4 (substr): world
Comparison (sv1 vs sv2): 0