漫谈C++ string(2):string_ref的实现

Overview

在c++的函数调用中传递字符串引用时非常常见的,但是通常被调函数通常不关心所传参数的实际实际类型,从而可能会发生以下状况:

  • 被调函数以std::string(包括std::string&)作为参数,如果调用者传入的是非std::string的数据类型的话,必然会发生数据拷贝
  • 被调函数以char*length作为参数,则会降低代码可读性和安全性,同时还无法使用标准库提供的一些函数
  • 被调函数是以模板实现的用以支持各种参数,但这通常会增加代码的复杂度和编译时间
  • 被调函数之间需要传递字符串的一部分(substr)时,也必然会发生数据拷贝

来看一个具体的例子:

1
2
3
4
5
std::string extract_part ( const std::string &bar ) {
return bar.substr ( 2, 3 );
}

if ( extract_part ( "ABCDEFG" ).front() == 'C' ) { /* do something */ }

在上述例子中,一个临时的string变量会被创建出来,然后通过引用传递的方式传入extract_part中,然后第二个临时string变量在调用substr之后会被创建出来,然后返回给调用者的时候也可能会产生数据拷贝(通过RVO可消除)。其实这两个临时变量不是必须要产生的,这说明在以string做为参数传递时经常容易产生不必要的拷贝,特别是对于大字符串来说,避免数据拷贝是很有必要的,但是标准库的COW实现也有问题,参见std::string的Copy-on-Write:不如想象中美好,所以很多大型项目或基础库都提供了StringReference的实现,如Boost::StringRef,LLVM的StringRef, Chromium的base::StringPiece等,在c++17中也新增了std::string_view来支持字符串引用。

Chrome StringPiece

Chrome中的StringPiece实现在string/string_piece.h中,它可以作为函数参数和返回值使用,一个StringPiece参数也可以接受std::string、const char*、常量字符串作为输入,这些输入都不会产生数据拷贝。
StringPiece的定义如下:

1
2
3
//strings/string_piece_forward.h
class BasicStringPiece;
typedef BasicStringPiece<std::string> StringPiece;

可以看到,StringPiece是BasicStringPiece关于std::string的实现,下面来看BasicStringPiece的实现。

1
2
3
4
5
6
7
8
9
10
11
12
//构造函数
constexpr BasicStringPiece(const value_type* str)
: ptr_(str), length_(!str ? 0 : CharTraits<value_type>::length(str)) {}
BasicStringPiece(const STRING_TYPE& str)
: ptr_(str.data()), length_(str.size()) {}
constexpr BasicStringPiece(const value_type* offset, size_type len)
: ptr_(offset), length_(len) {}
BasicStringPiece(const typename STRING_TYPE::const_iterator& begin,
const typename STRING_TYPE::const_iterator& end) {}
//成员变量
const value_type* ptr_;
size_type length_;

可以看到,它有两个成员变量,分别是指向的数据以及数据的长度,所以它的传递和拷贝值需要拷贝相应的指针,而不需要拷贝指向的数据,这种实现方式也要求调用者要确保指向数据的有效性。如前文所说,它可以接收const char*const std::string&作为参数。
下面来看它的部分成员函数实现,首先是substr:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  // substr.
BasicStringPiece substr(size_type pos,
size_type n = BasicStringPiece::npos) const {
return internal::substr(*this, pos, n);
}

//最终调用函数
template<typename STR>
BasicStringPiece<STR> substrT(const BasicStringPiece<STR>& self,
size_t pos,
size_t n) {
if (pos > self.size()) pos = self.size();
if (n > self.size() - pos) n = self.size() - pos;
return BasicStringPiece<STR>(self.data() + pos, n);
}

BasicStringPiece::substr最终调用的是substrT这个函数,可以看到的是,它不会产生额外的数据拷贝,只会拷贝指向数据的指针。

References