高效操作字串的String Reference類

Horky發表於2016-02-06

如下面的程式碼中一個函式接受一個std::string常量引用,在其函式內部需要使用std::string的一些函式操作字串。

void foo(const std::string& param) {
  ......
}

引數使用的是常量引用,如果傳入一個std::string就不需要額外的拷貝。但是如果呼叫時傳入的是一個字串常量,這時必然會生成一個std::string物件,並且會有一次記憶體拷貝。

關於字串發生拷貝可以使用下面的程式碼測試:

#include <string>
namespace {
const char* sString = "123456";

void foo(const std::string& str) {
  printf ("input string address: %x\n", str.c_str() );
}
}  // namespace

int main()
{
  printf("const string address: %x\n", sString);
  foo(sString);
  return 0;
}

這是一個很典型的問題,事實上只要不修改字串內容,並不需要另外複製一份。特別是對一些比較大的字串,避免拷貝對記憶體和效能都有極大的好處。於是Jeffrey Yasskin提出一個String reference : a non-owning reference to a string. 很多大型的專案都提供了各自的實現,包括Boost::StringRef, LLVM的StringRef, Chromium的base::StringPiece。

(STL也有字串的Copy-on-Write的實現,但要看實現版本。這裡有更多的說明:std::string的Copy-on-Write:不如想象中美好。)


以下用StringPiece為例來介紹。它的原理也很簡單,StringPiece內部僅持有字串指標和一個長度值,然後參照std::string的介面提供一組操作函式。比如find, find_first_of, rfind, substr. 

  template <typename STRING_TYPE> class BasicStringPiece {
 public:
  ......
  BasicStringPiece substr(size_type pos,
                          size_type n = BasicStringPiece::npos) const {
    return internal::substr(*this, pos, n);
  }

 protected:
  const value_type* ptr_;
  size_type     length_;
};
typedef BasicStringPiece<std::string> StringPiece;

它析構時不會釋放字串,因為StringPiece不持有字串的所有權,也就是字串物件本身的生命週期一定要長於StringPiece物件。

可以看到StringPiece是一個模板類,主要是因為它需要同時支援以std::string和C string傳遞的引數。


以上面提供的取子串的操作為例,一個基本思路就是建立新的StringPiece, 把它的指標指到子串的起始位置,再將長度設定為子串的長度就可以了。在這個過程並沒有出現字串的拷貝。

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);
}


Enjoy it!



相關文章