学习LLVM数据结构:StringRef
在现代 C++ 程序开发中,选择合适的数据结构,对于程序性能和内存安全都至关重要。LLVM 项目中,有一大块内容便是设计用于内部开发的高效数据结构。
本文将以 cppreference 的格式,介绍这些实用的数据结构。你既可以将其作为学习参考,也可以作为学习 LLVM 的补充材料。
基本介绍
llvm::StringRef 定义在 llvm/ADT/StringRef.h 头文件中。 它的声明为:
1 | class StringRef; |
和 ArrayRef 类似,StringRef 是一种轻量级的字符串引用类型,它用于实现高效地表示和操作字符串数据,尤其是在一些高频处理字符串,但同时不希望维护实际内存开销地场景下。 它是不可变引用,主要用于向函数内传递字符串同时避免深拷贝。它只包含了指向字符串的指针和字符串的长度信息,从而使得对它的操作直接而高效。
特点
- 轻量级:只存储一个指向字符串的指针和字符串的长度,不存储实际的数据,所以拷贝时很高效。
- 简单易用:支持许多常用的字符串操作,比如比较、查找和子串提取,使用很方便。
- 操作一致:它的大多数操作,和标准库中 string 保持一致。
与标准库 string 对比
- 所有权:
StringRef是对字符串数据的引用,不拥有字符串的所有权。需要确保StringRef对象的生命周期,小于实际引用字符串的生命周期,否则会带来悬空引用。 - 大小:
StringRef只占用很小的内存,和实际字符串相比可以忽略不计。std::string不仅包括了指针、长度,还包括了数据的内存管理信息,它在内部维护了一个动态分配的缓存区来存储字符串内容。 - 可变性:
StringRef是不可变的,它引用的内容不能被修改。而std::string则提供了完整的可操作空间。 - 性能:
StringRef不涉及内存分配,在传递和使用时,具有更高的性能,特别是在函数传参时,可以避免深拷贝。而std::string在按值传参时,会对保存字符串的内存做完整的复制。现在编译器可以通过移动语义和短字符串优化等方式来减少深拷贝的性能开销,但整体上,传递引用还是要更轻量级。
数据成员
由于 llvm::StringRef 只拥有数据的引用,而不管理实际字符串的内存,所以它的结构很简单。
1 | class StringRef { |
成员方法
事实上,使用 const std::string & 基本可以取代 llvm::StringRef,然而,StringRef 的另一个优势是,它提供了更多更易用的操作方法,比如 split 函数。
构造
StringRef 可以方便地从字符串字面量、std::string 或 char * 指针来初始化。
1 | // 从字符串字面量初始化 |
迭代器
提供了迭代器 begin() 和 end(),以及返回字符指针的 bytes_begin() 和 bytes_end(),这是为了处理宽字符编码的字符串。
元素访问
1 | // 拿到字符串原始位置 |
比较
1 | llvm::StringRef Str1, Str2; |
编辑距离
编辑距离(edit distance)是指将一个字符串完全改为另一个字符串时,所需要的最小单字符操作的次数。这些操作可以是: - 插入一个字符 - 删除一个字符 - 替换一个字符(分为将替换看作一次操作,还是看作两次操作)
1 | llvm::StringRef Str1, Str2; |
获取副本
使用 str() 来获取一个 std::string 类型的副本。 使用 string_view() 来获取一个 std::string_view 类型的副本。 同时,也提供了 copy 函数,来获取一个 StringRef 的副本,但需要提供分配器,用于分配新的空间:
1 |
|
检查
1 | llvm::StringRef Str; |
查找
1 | llvm::StringRef Str; |
辅助函数
1 | llvm::StringRef Str; |
切片
1 | // 截取子字符串的操作,实际上是修改指针指向位置和长度的结果 |
注意事项
引用失效
和 llvm::ArrayRef 一样,llvm::StringRef 本身是一个引用类型,所以它会存在引用失效的风险。 另外,也不建议直接返回一个内部临时对象引用的 llvm::StringRef 类型,可以返回经过调整,但指向外部字符串的新的 llvm::StirngRef 类型。 它的生命周期一定要短于它所引用的字符串的生命周期,从而避免悬挂引用。
不可修改
它是对目标字符串的常量引用,所以不可以直接通过它修改目标字符串。不过,可以通过 data 来获取到它原始字符串的指针,从而去修改原始字符串,但这样存在一些潜在的风险,并不是推荐的做法。如果需要修改字符串,使用 std::string & 可能更好。
字符编码
llvm::StringRef 并不处理字符编码问题,它能提供的最大能力就是使用迭代器时,可以选择按字符迭代,还是获得底层的字符指针,由开发者自己选择怎么解析。 所以,如果需要处理宽字符编码的字符串,需要额外做一些包装。
另见
llvm::StringLiteral:处理字符串字面量的子类,高效管理字符串字面量。llvm::ArrayRef:顺序型容器的只读引用,见 学习 LLVM 数据结构:ArrayRef。
本文同步发布在知乎账号下:学习LLVM数据结构-StringRef - 知乎











