0. 引言

在性能敏感的应用场景中,如网络库和系统编程,频繁的 std::string 字符串拷贝会导致显著的性能开销。C++17 引入的 std::string_view 能有效减少不必要的拷贝操作。然而,由于项目要求只能使用 C++14,因此我们需要自己实现一个类似的 StringView 类来代替 std::string_view,以优化性能。

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

适用场景

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

1. 完整代码

#include <algorithm>
#include <cstring>
#include <iostream>
#include <stdexcept>
#include <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;

  explicit StringView(const char* str) : data_(str), size_(str ? std::strlen(str) : 0) {}

  StringView(const char* str, std::size_t len) : data_(str), size_(str ? len : 0) {}

  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);
    return StringView(data_ + pos, len);
  }

  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 风格字符串、指定长度、或 std::string 构造。
  • 对空指针(nullptr)进行处理,确保安全。

基本操作:

  • 提供 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

通过此实现,我们能够有效避免字符串拷贝带来的性能问题,并且在不引入额外内存分配的情况下,提供灵活和高效的字符串视图操作。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐