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
應該像int
或double
那樣按值傳遞(相對於按引用、指標傳遞,譯者注),因為string_view
物件本身只佔用很小的記憶體。 -
string_view
指向的字串未必以NULL
字元結尾。因此,如下的寫法是不安全的:printf("%s\n", sv.data()); // 別這樣寫
然而,如下寫法是可以的:
printf("%.*s\n", static_cast<int>(sv.size()), sv.data());
-
你可以像列印
string
或const char*
一樣直接列印string_view
:std::cout << "Took '" << s << "'";
-
在大部分情況下,你可以安全地將現有函式的
const string&
或NULL
結尾的const char*
型別的形參直接轉換為string_view
。我們見過的唯一例外是,如果將函式地址賦值給某函式指標,那麼會遇到“函式指標型別不匹配”的編譯錯誤。