目录
[一、 StringPiece.h](#一、 StringPiece.h)
[1. StringPiece.h 整体定位](#1. StringPiece.h 整体定位)
[2. 逐模块拆解代码](#2. 逐模块拆解代码)
[3. 核心使用场景与坑点](#3. 核心使用场景与坑点)
[总结(StringPiece.h 核心要点)](#总结(StringPiece.h 核心要点))
一、 StringPiece.h
先贴出完整代码,再逐部分解释:
cpp
// 代码取自 PCRE 库的 pcre_stringpiece.h
//
// 版权所有 (c) 2005, Google Inc.
// 保留所有权利。
//
// 遵循以下条件的情况下,允许以源代码和二进制形式重新分发和使用本软件,
// 无论是否经过修改:
//
// * 源代码的重新分发必须保留上述版权声明、本条件列表以及以下免责声明。
// * 二进制形式的重新分发必须在随分发提供的文档和/或其他材料中重现
// 上述版权声明、本条件列表以及以下免责声明。
// * 未经事先书面许可,不得使用 Google Inc. 的名称或其贡献者的名称
// 来认可或推广源自本软件的产品。
//
// 本软件按"原样"提供,不附带任何明示或暗示的担保,包括但不限于
// 适销性和特定用途适用性的暗示担保。在任何情况下,版权所有者或贡献者
// 均不对任何直接、间接、偶然、特殊、惩戒性或后果性的损害(包括但不限于
// 采购替代商品或服务;使用损失、数据丢失或业务中断)承担责任,
// 无论以何种方式引起,且基于任何责任理论(包括合同、严格责任或侵权
// (包括疏忽或其他)),即使已被告知可能发生此类损害。
//
// 作者:Sanjay Ghemawat
//
// 一个类字符串对象,指向另一块内存区域中的字符串(仅引用,不拷贝)。
// 适用于设计这样的接口:允许调用方轻松传入 "const char*" 或 "string" 类型的参数,
// 无需额外拷贝数据。
//
// 唉!真希望 C++ 的字符串字面量能自动是 "string" 类型(而不是 const char*)。
#ifndef MUDUO_BASE_STRINGPIECE_H
#define MUDUO_BASE_STRINGPIECE_H
#include <string.h> // 内存操作和字符串函数(memcmp/strlen 等)
#include <iosfwd> // 用于 ostream 前向声明(避免包含完整的 iostream)
#include "muduo/base/Types.h" // muduo 基础类型定义
namespace muduo
{
// 用于接收 C 风格字符串参数的辅助类(支持隐式转换)
// 可拷贝(未继承 noncopyable,默认支持拷贝构造和赋值)
class StringArg // copyable
{
public:
// 构造函数:接收 const char* 类型
StringArg(const char* str)
: str_(str)
{ }
// 构造函数:接收 std::string 类型(隐式转换)
StringArg(const string& str)
: str_(str.c_str())
{ }
// 获取 C 风格字符串指针
const char* c_str() const { return str_; }
private:
const char* str_; // 存储字符串指针(仅引用,不管理内存)
};
// 轻量级字符串引用类(StringPiece/字符串视图)
// 核心特性:不拷贝字符串数据,仅保存指向字符串的指针和长度,
// 避免不必要的内存拷贝,提升性能;但需注意生命周期(不能引用已释放的内存)
class StringPiece {
private:
const char* ptr_; // 指向字符串数据的指针(仅引用,不拥有)
int length_; // 字符串长度(避免重复调用 strlen)
public:
// 提供非显式的单参数构造函数,使得用户在需要 StringPiece 的地方,
// 可以直接传入 "const char*" 或 "string" 类型(隐式转换)
StringPiece()
: ptr_(NULL), length_(0) { } // 空构造:指向 NULL,长度 0
StringPiece(const char* str) // 从 const char* 构造
: ptr_(str), length_(static_cast<int>(strlen(ptr_))) { }
StringPiece(const unsigned char* str) // 从 unsigned char* 构造(类型转换)
: ptr_(reinterpret_cast<const char*>(str)),
length_(static_cast<int>(strlen(ptr_))) { }
StringPiece(const string& str) // 从 std::string 构造
: ptr_(str.data()), length_(static_cast<int>(str.size())) { }
StringPiece(const char* offset, int len) // 从指定指针+长度构造
: ptr_(offset), length_(len) { }
// 重要注意事项:
// data() 可能返回指向包含嵌入 NUL 字符('\0')的缓冲区的指针,
// 且返回的缓冲区可能以 NUL 结尾,也可能不以 NUL 结尾。
// 因此,将 data() 传递给期望以 NUL 结尾的字符串的函数通常是错误的。
// 如果确实需要这样做,请使用 "as_string().c_str()"(会生成拷贝)。
// 更好的方式是修改你的函数,使其不依赖 NUL 结尾。
const char* data() const { return ptr_; } // 获取字符串数据指针
int size() const { return length_; } // 获取字符串长度
bool empty() const { return length_ == 0; } // 判断是否为空字符串
const char* begin() const { return ptr_; } // 获取起始迭代器(指针)
const char* end() const { return ptr_ + length_; } // 获取结束迭代器(指针)
// 清空 StringPiece:重置为 NULL 和长度 0
void clear() { ptr_ = NULL; length_ = 0; }
// 设置为指定缓冲区指针+长度
void set(const char* buffer, int len) { ptr_ = buffer; length_ = len; }
// 设置为指定 const char* 字符串(自动计算长度)
void set(const char* str) {
ptr_ = str;
length_ = static_cast<int>(strlen(str));
}
// 设置为指定 void* 缓冲区+长度(类型转换)
void set(const void* buffer, int len) {
ptr_ = reinterpret_cast<const char*>(buffer);
length_ = len;
}
// 重载 [] 运算符:访问指定位置的字符(无越界检查)
char operator[](int i) const { return ptr_[i]; }
// 移除前缀:跳过前 n 个字符(仅移动指针、减少长度,无数据拷贝)
void remove_prefix(int n) {
ptr_ += n;
length_ -= n;
}
// 移除后缀:截断最后 n 个字符(仅减少长度,无数据拷贝)
void remove_suffix(int n) {
length_ -= n;
}
// 重载 == 运算符:比较两个 StringPiece 是否相等
// 先比较长度,再比较内容(memcmp 保证二进制安全,支持含 NUL 的字符串)
bool operator==(const StringPiece& x) const {
return ((length_ == x.length_) &&
(memcmp(ptr_, x.ptr_, length_) == 0));
}
// 重载 != 运算符:基于 == 实现
bool operator!=(const StringPiece& x) const {
return !(*this == x);
}
// 宏定义:批量生成比较运算符(<、<=、>=、>)
// 核心逻辑:先按最短长度比较内容(memcmp),若内容相等则比较长度
#define STRINGPIECE_BINARY_PREDICATE(cmp,auxcmp) \
bool operator cmp (const StringPiece& x) const { \
// 取两者中较短的长度进行 memcmp 比较 \
int r = memcmp(ptr_, x.ptr_, length_ < x.length_ ? length_ : x.length_); \
// 比较规则:1. 内容比较结果满足 auxcmp;或 2. 内容相等但长度满足 cmp \
return ((r auxcmp 0) || ((r == 0) && (length_ cmp x.length_))); \
}
STRINGPIECE_BINARY_PREDICATE(<, <); // 生成 operator<
STRINGPIECE_BINARY_PREDICATE(<=, <); // 生成 operator<=
STRINGPIECE_BINARY_PREDICATE(>=, >); // 生成 operator>=
STRINGPIECE_BINARY_PREDICATE(>, >); // 生成 operator>
#undef STRINGPIECE_BINARY_PREDICATE // 释放宏定义
// 比较函数:返回值规则同 strcmp
// < 0: 当前对象 < x;= 0: 相等;> 0: 当前对象 > x
int compare(const StringPiece& x) const {
// 先比较最短长度的内容
int r = memcmp(ptr_, x.ptr_, length_ < x.length_ ? length_ : x.length_);
if (r == 0) {
// 内容相等时,比较长度
if (length_ < x.length_) r = -1;
else if (length_ > x.length_) r = +1;
}
return r;
}
// 转换为 std::string(会拷贝数据,生成新字符串)
string as_string() const {
return string(data(), size());
}
// 将内容拷贝到目标 std::string 中(覆盖目标原有内容)
void CopyToString(string* target) const {
target->assign(ptr_, length_);
}
// 判断当前 StringPiece 是否以 x 为前缀
bool starts_with(const StringPiece& x) const {
return ((length_ >= x.length_) && (memcmp(ptr_, x.ptr_, x.length_) == 0));
}
};
} // namespace muduo
// ------------------------------------------------------------------
// 用于创建存储 StringPiece 的 STL 容器的辅助函数/特化
// 重要提醒:StringPiece 的生命周期必须短于其引用的底层字符串(char* 或 string)。
// 如果不满足这个条件,你不能安全地将 StringPiece 存储到 STL 容器中
// ------------------------------------------------------------------
#ifdef HAVE_TYPE_TRAITS
// 对 StringPiece 进行类型特性特化,使得某些 STL 实现下的 vector<StringPiece> 效率更高
// (标记为 POD 类型、拥有平凡的构造/拷贝/赋值/析构函数,STL 可做内存优化)
template<> struct __type_traits<muduo::StringPiece> {
typedef __true_type has_trivial_default_constructor; // 平凡默认构造函数
typedef __true_type has_trivial_copy_constructor; // 平凡拷贝构造函数
typedef __true_type has_trivial_assignment_operator; // 平凡赋值运算符
typedef __true_type has_trivial_destructor; // 平凡析构函数
typedef __true_type is_POD_type; // 是 POD 类型
};
#endif
// 重载输出运算符,允许将 StringPiece 输出到日志/流中(如 cout、日志流)
std::ostream& operator<<(std::ostream& o, const muduo::StringPiece& piece);
#endif // MUDUO_BASE_STRINGPIECE_H
1. StringPiece.h 整体定位
StringPiece(字符串视图)是 Muduo 中高频使用的基础工具类,核心解决两个问题:
- 零拷贝字符串传递 :避免
const char*和std::string相互转换时的内存拷贝开销(比如网络数据解析、日志格式化场景); - 统一字符串接口 :兼容
const char*和std::string两种字符串类型,无需为函数写大量重载版本;它源自 Google 的实现,是 "视图模式" 在字符串场景的经典应用,仅引用原字符串内存,不拷贝、不管理内存,兼顾效率和易用性。
2. 逐模块拆解代码
(1)辅助类:StringArg(字符串参数适配器)
cpp
// 用于接收函数参数:统一const char*和std::string,无需重载
class StringArg // copyable(轻量级,仅存指针,拷贝无开销)
{
public:
// 接收const char*
StringArg(const char* str) : str_(str) { }
// 接收std::string(自动转const char*)
StringArg(const string& str) : str_(str.c_str()) { }
// 返回底层C字符串
const char* c_str() const { return str_; }
private:
const char* str_; // 仅存储指针,无拷贝
};
核心解释:
- 设计目的:作为函数参数类型时,允许调用方直接传
const char*或std::string,无需为函数写两个重载版本; - 轻量级:仅存储一个指针,拷贝 / 赋值无开销(因此标记为 copyable);
- 典型使用场景:
cpp
// 无需写两个重载:void func(const char*) / void func(const string&)
void func(muduo::StringArg arg) {
const char* str = arg.c_str(); // 统一处理
}
// 调用方可以自由传参
func("hello"); // const char*
func(std::string("hi"));// std::string
(2)核心类:StringPiece(字符串视图)
① 核心成员变量(视图的本质)
cpp
private:
const char* ptr_; // 指向原字符串的起始地址(不管理内存)
int length_; // 字符串长度(缓存,避免多次strlen)
关键特性:
ptr_不一定以\0结尾!这是新手最容易踩的坑 ------StringPiece支持 "非空终止的字符串"(比如网络缓冲区中截取的一段数据),因此不能直接把ptr_传给要求\0终止的函数(如strcpy);length_缓存长度:避免每次调用size()都执行strlen(ptr_)(strlen是 O (n) 操作,高频场景下效率低)。
② 构造函数(隐式转换,方便传参)
cpp
// 空视图
StringPiece() : ptr_(NULL), length_(0) { }
// 接收const char*(自动计算长度,依赖\0终止)
StringPiece(const char* str) : ptr_(str), length_(static_cast<int>(strlen(ptr_))) { }
// 接收unsigned char*(类型转换)
StringPiece(const unsigned char* str) : ptr_(reinterpret_cast<const char*>(str)), length_(static_cast<int>(strlen(ptr_))) { }
// 接收std::string(直接取data()和size(),不拷贝)
StringPiece(const string& str) : ptr_(str.data()), length_(static_cast<int>(str.size())) { }
// 接收"指针+长度"(核心!支持非\0终止的字符串)
StringPiece(const char* offset, int len) : ptr_(offset), length_(len) { }
- 非显式构造(无
explicit):允许隐式转换,比如函数参数要StringPiece时,可直接传"hello"或std::string("hi"); - "指针 + 长度" 构造:是
StringPiece最核心的用法(比如从网络缓冲区截取[buf+10, buf+20]这段数据,无需拷贝,直接传指针 + 长度)。
③ 基础访问方法(只读,不修改原字符串)
cpp
// 返回指针(不一定\0终止!)
const char* data() const { return ptr_; }
// 返回长度(O(1))
int size() const { return length_; }
// 是否为空
bool empty() const { return length_ == 0; }
// 迭代器风格的首尾指针
const char* begin() const { return ptr_; }
const char* end() const { return ptr_ + length_; }
如果需要把 StringPiece 传给要求 \0 终止的函数(如 printf("%s")),必须先转 std::string:
cpp
muduo::StringPiece sp("hello", 3); // "hel",无\0
// 错误:printf会读到\0为止,可能越界
printf("%s\n", sp.data());
// 正确:转为std::string(会自动加\0)
printf("%s\n", sp.as_string().c_str());
④ 视图修改方法(仅修改视图,不修改原字符串)
cpp
// 清空视图
void clear() { ptr_ = NULL; length_ = 0; }
// 设置视图指向的字符串
void set(const char* buffer, int len) { ptr_ = buffer; length_ = len; }
void set(const char* str) { ptr_ = str; length_ = static_cast<int>(strlen(str)); }
void set(const void* buffer, int len) { ptr_ = reinterpret_cast<const char*>(buffer); length_ = len; }
// 移除前缀(比如跳过协议头)
void remove_prefix(int n) { ptr_ += n; length_ -= n; }
// 移除后缀(比如跳过校验位)
void remove_suffix(int n) { length_ -= n; }
核心特点 :这些操作只改变 ptr_ 和 length_,不碰原字符串的内存 ------ 比如 remove_prefix(2) 只是把视图从 "hello" 变成 "llo",原字符串 "hello" 完全不变。
⑤ 运算符重载(比较与访问)
随机访问 :char operator[](int i) const { return ptr_[i]; }直接访问第 i 个字符,无边界检查(和 std::string 一致)。
相等 / 不等判断:
cpp
bool operator==(const StringPiece& x) const {
return ((length_ == x.length_) && (memcmp(ptr_, x.ptr_, length_) == 0));
}
bool operator!=(const StringPiece& x) const { return !(*this == x); }
核心逻辑:先比较长度,再用 memcmp 逐字节比较(不依赖 \0),比 strcmp 更安全(支持非空终止字符串)。
大小比较(宏定义简化代码):
cpp
#define STRINGPIECE_BINARY_PREDICATE(cmp,auxcmp) \
bool operator cmp (const StringPiece& x) const { \
// 先比较公共长度的字节(取较短的长度)
int r = memcmp(ptr_, x.ptr_, length_ < x.length_ ? length_ : x.length_); \
// 公共部分不等:按memcmp结果;公共部分相等:按长度比较
return ((r auxcmp 0) || ((r == 0) && (length_ cmp x.length_))); \
}
// 生成 <、<=、>=、> 运算符
STRINGPIECE_BINARY_PREDICATE(<, <);
STRINGPIECE_BINARY_PREDICATE(<=, <);
STRINGPIECE_BINARY_PREDICATE(>=, >);
STRINGPIECE_BINARY_PREDICATE(>, >);
#undef STRINGPIECE_BINARY_PREDICATE
宏解析 :比如生成 operator< 时,逻辑是:
- 用
memcmp比较两个字符串的公共长度(比如"apple"和"app"先比前 3 个字符); - 如果公共部分不等,直接返回
memcmp的结果(<0则true); - 如果公共部分相等,比较长度(短的更小,比如
"app"<"apple")。
⑥ 核心工具方法
cpp
// 通用比较(返回-1/0/1,和strcmp一致)
int compare(const StringPiece& x) const { ... }
// 转为std::string(会拷贝内存,仅在必要时使用)
string as_string() const { return string(data(), size()); }
// 拷贝到已有string(避免临时对象,效率更高)
void CopyToString(string* target) const { target->assign(ptr_, length_); }
// 判断前缀(比如检查协议头是否为"HTTP/")
bool starts_with(const StringPiece& x) const {
return ((length_ >= x.length_) && (memcmp(ptr_, x.ptr_, x.length_) == 0));
}
(3)STL 优化与流输出
cpp
// 特化类型特征:标记StringPiece为POD类型,让vector<StringPiece>更快
#ifdef HAVE_TYPE_TRAITS
template<> struct __type_traits<muduo::StringPiece> {
typedef __true_type has_trivial_default_constructor;
typedef __true_type has_trivial_copy_constructor;
typedef __true_type has_trivial_assignment_operator;
typedef __true_type has_trivial_destructor;
typedef __true_type is_POD_type;
};
#endif
// 支持流输出(比如日志打印)
std::ostream& operator<<(std::ostream& o, const muduo::StringPiece& piece);
核心作用 :标记为 POD(Plain Old Data)类型后,STL 容器(如 vector<StringPiece>)会采用更高效的内存布局和拷贝方式,避免额外开销。
3. 核心使用场景与坑点
(1)高效字符串参数传递
cpp
// 传统方式:需要写两个重载
void log(const char* msg) { /* ... */ }
void log(const std::string& msg) { /* ... */ }
// StringPiece方式:一个函数搞定,零拷贝
void log(muduo::StringPiece msg) {
// 直接使用msg.data()和msg.size(),无拷贝
printf("[LOG] %.*s\n", msg.size(), msg.data());
}
// 调用方自由传参,无拷贝
log("hello world"); // const char*
log(std::string("test log")); // std::string
log(muduo::StringPiece(buf, 10)); // 网络缓冲区片段(非\0终止)
(2)关键坑点:生命周期
StringPiece 仅引用原字符串,生命周期不能超过原字符串:
cpp
// 错误示例:原字符串是临时对象,函数返回后销毁,sp悬空
muduo::StringPiece bad_func() {
std::string temp = "hello";
return muduo::StringPiece(temp); // temp析构后,ptr_指向无效内存
}
// 正确示例:原字符串生命周期更长
muduo::StringPiece good_func(const std::string& str) {
return muduo::StringPiece(str); // str由调用方管理,生命周期安全
}
StringPiece是零拷贝字符串视图,仅引用原字符串内存,不拷贝、不管理内存,核心用于高频字符串传递场景;- 核心特点:兼容
const char*/std::string/ 非空终止字符串,ptr_不一定以\0结尾,使用时需注意边界和生命周期; - 辅助类
StringArg和宏定义简化了接口设计,兼顾效率(O (1) 长度访问、memcmp 比较)和易用性(隐式转换、统一接口)。
总结(StringPiece.h 核心要点)
StringPiece是零拷贝字符串视图,仅引用原字符串内存,不拷贝、不管理内存,核心用于高频字符串传递场景;- 核心特点:兼容
const char*/std::string/ 非空终止字符串,ptr_不一定以\0结尾,使用时需注意边界和生命周期; - 辅助类
StringArg和宏定义简化了接口设计,兼顾效率(O (1) 长度访问、memcmp 比较)和易用性(隐式转换、统一接口)。