1. 程式人生 > >2018-09-18-totw1-string-view.md

2018-09-18-totw1-string-view.md

C++貼士 #1: string_view

什麼是string_view,以及為什麼你應該關心?

如果需要寫一個函式接受(常量)字串為輸入引數,你有四個選項:其中兩個你已經知道了,另外兩個你也許還不知道:

void TakesCharStar(const char* s);             // C風格
void TakesString(const string& s);             // 舊標準(C++17以前) C++風格
void TakesStringView(absl::string_view s);     // Abseil C++風格
void TakesStringView
(std::string_view s); // C++17 C++風格

當函式呼叫時已經有對應格式的字串存在,前兩種方式最適合;但如果需要型別轉換時怎麼辦(從const char*轉換為string,或反之)?

string轉換為const char*需要(高效但不方便的)函式c_str()

void AlreadyHasString(const string& s) {
  TakesCharStar(s.c_str());               // 顯式轉換
}

Callers needing to convert a const char* to a string don’t need to do anything additional (the good news) but will invoke the creation of a (convenient but inefficient) temporary string, copying the contents of that string (the bad news):

const char*轉換為string不需要額外操作(好訊息),但會(方便但低效地)建立一個臨時字串,拷貝原來字串的內容(壞訊息):

void AlreadyHasCharStar(const char* s) {
  TakesString(s); // 編譯器會複製一份s
}

怎麼解決?

Google推薦使用string_view來接受字串引數。這個型別比C++17要早——在C++17環境中你應該使用std::string_view,在非C++17環境中你應該使用absl::string_view

一個string_view型別的變數可以被想象成一個“映象”,映射了一段已經存在的字元列表。更明確地說,一個string_view

僅僅包含一個指標和一個長度,用以定位一個字元資料區間。string_view既不擁有這些資料,又不能修改這段儲存。因此,複製string_view是淺拷貝,字串內容不會被複制。

string_view可以從const char*const string&隱式構造而成。又因為string_view不會複製字串,構造string_view不會有O(n)的記憶體代價。以const string&構造string_view時,建構函式時間複雜度為O(1)。以const char*構造string_view時,建構函式會自動呼叫strlen()(或者你可以用雙參形式的string_view建構函式)。

void AlreadyHasString(const string& s) {
  TakesStringView(s); // 沒有顯式型別轉換;方便!
}

void AlreadyHasCharStar(const char* s) {
  TakesStringView(s); // 沒有複製;高效!
}

因為string_view不擁有其指向的資料,所以string_view(就像const char*)指向的字串需要有超出該string_view的生存期。這意味著儲存string_view總是需要問個問題:你得證明string_view指向的資料的生存期超出string_view的生存期

如果你的API只需要在單次函式呼叫中使用字串資料,且不需要修改該字串資料,(讓函式(譯者注))接收一個string_view就足夠了。如果你需要修改資料或在以後訪問資料,那麼你需要用string(my_string_view)string_view顯式轉換為C++字串。

向現有程式碼庫中新增string_view並不總是正確的事:如果在函式內需要將字串以string或以NULL結尾的const char*傳給下一級函式,那麼將本級函式引數改為string_view可能會是低效的。對於string_view,推薦先在工具程式碼中採用,進而逐步向其呼叫端推廣;或者在全新專案中統一使用string_view

附加說明

  • 與其他字串型別不同,string_view應該像intdouble那樣按值傳遞(相對於按引用、指標傳遞,譯者注),因為string_view物件本身只佔用很小的記憶體。

  • string_view指向的字串未必以NULL字元結尾。因此,如下的寫法是不安全的:

    printf("%s\n", sv.data()); // 別這樣寫
    

    然而,如下寫法是可以的:

    printf("%.*s\n", static_cast<int>(sv.size()), sv.data());
    
  • 你可以像列印stringconst char*一樣直接列印string_view

    std::cout << "Took '" << s << "'";
    
  • 在大部分情況下,你可以安全地將現有函式的const string&NULL結尾的const char*型別的形參直接轉換為string_view。我們見過的唯一例外是,如果將函式地址賦值給某函式指標,那麼會遇到“函式指標型別不匹配”的編譯錯誤。