string_View理解与用法(一)
什么是string_view
當你創建一個將(常量)字符串作為參數的函數時,你有四個選擇,你可能知道兩個,但不知道另外兩個:
void TakesCharStar(const char* s); // C convention void TakesString(const string& s); // Old Standard C++ convention void TakesStringView(absl::string_view s); // Abseil C++ convention void TakesStringView(std::string_view s); // C++17 C++ convention當調用者已經有已提供的格式的字符串時即提供的字符串類型完全匹配時(對于第1個函數,調用者提供的字符串類型為:const char*,如:const char *p = "test";? 對于第2個函數,調用者提供的字符串類型為const string,如: const string p = "test"; ),前兩者各自對應的方法最有效,但是當需要進行轉換(如從const char *到string 或 string到char *)時發生什么呢?
調用者需要將字符串轉換為const char *時,需要用(高效但不方便)c_str()函數:
void AlreadyHasString(const string& s) {TakesCharStar(s.c_str()); // explicit conversion }調用者需要將const char *轉換為字符串時,不需要做任何其他操作(這是好消息);但是將創建臨時字符串(方便但效率低),并復制該字符串的內容(這是壞消息)。
string有什么缺點?
本節內容主要摘自博客【現代C++】性能控的工具箱之string_view。
在數據傳遞中減少拷貝是提高性能的最常用辦法。在C中指針是完成這一目的的標準數據結構,而在C++中引入了安全性更高的引用類型。所以在C++中若傳遞的數據僅僅可讀,const string&成了C++天然的方式。但這并非完美,從實踐上來看,它至少有以下幾方面問題:
- 字符串字面值、字符數組、字符串指針的傳遞依然要數據拷貝
這三類低級數據類型與string類型不同,傳入時編譯器要做隱式轉換,即需要拷貝這些數據生成string臨時對象。const string&指向的實際上是這個臨時對象。通常字符串字面值較小,性能損失可以忽略不計;但字符串指針和字符數組某些情況下可能會比較大(比如讀取文件的內容),此時會引起頻繁的內存分配和數據拷貝,影響程序性能。
- substr O(n)復雜度
substr是個常用的函數,好在std::string提供了這個函數,美中不足的時每次都要返回一個新生成的子串,很容易引起性能熱點。實際上我們本意不是要改變原字符串,為什么不在原字符串基礎上返回呢?
怎么辦
在C++17中引入了string_view,能很好的解決以上兩個問題。
std::string_view是C++ 17標準中新加入的類,正如其名,它提供一個字符串的視圖,即可以通過這個類以各種方法“觀測”字符串,但不允許修改字符串。由于它只讀的特性,它并不真正持有這個字符串的拷貝,而是與相對應的字符串共享這一空間。即——構造時不發生字符串的復制(具體請參考《詳解C++17下的string_view》)。同時,你也可以自由的移動這個視圖,移動視圖并不會移動原定的字符串。
- 通過調用 string_view 構造器可將字符串轉換為 string_view 對象。string 可隱式轉換為 string_view。
- string_view 是只讀的輕量對象,它對所指向的字符串沒有所有權。
- string_view通常用于函數參數類型,可用來取代 const char* 和 const string&。string_view 代替 const string&,可以避免不必要的內存分配。
- string_view的成員函數即對外接口與 string 相類似,但只包含讀取字符串內容的部分。
string_view::substr()的返回值類型是string_view,不產生新的字符串,不會進行內存分配。string::substr()的返回值類型是string,產生新的字符串,會進行內存分配。 - string_view字面量的后綴是 sv。(string字面量的后綴是 s)
輸出:
s1: 3 "abc" s2: 8 "abc^@^@def"以上例子能很好看清二者的語義區別,\0對于字符串而言,有其特殊的意義,即表示字符串的結束,字符串視圖根本不care,它關心實際的字符個數。
Google首選通過stringview接受這樣的字符串參數。這是C++17的“pre-adopted”類型,在C++17的構建中,您應該使用std::string_view,在任何不依賴C++17的代碼中,您應該使用absl::string_view(Abseil是Google開源的C++庫)。
string_view類的實例可以看作是現有字符串緩沖區的“視圖”。具體來說,string_view僅由一個指針和一個長度組成,用于標記不是string _view擁有且不能被該視圖修改的字符串數據部分。所以,復制string_view是一項淺層的操作:不復制任何字符串數據。
string_view有來自const char * 和 const string&的隱式轉換構造函數,并且由于string_view不拷貝,因此進行淺拷貝不產生O(n)內存損失。在傳遞cosnt string&的情況下,構造函數在O(1)時間進行。在傳遞const char*的情況下,構造函數會自動調用strlen()(或者你可以使用具有兩個參數的string_view構造函數)。
void AlreadyHasString(const string& s) {TakesStringView(s); // no explicit conversion; convenient! }void AlreadyHasCharStar(const char* s) {TakesStringView(s); // no copy; efficient! }因為string_view不擁有數據,所以string_view所指的任何字符串必須具有已知的生命周期,并且必須比string_view本身生命周期更長。這意味著使用string_view進行存儲通常是有問題的:你需要一些證據證明基礎數據的生命周期將超過string_view。
如果你的API僅需在一次調用中引用字符串數據,而無需修改數據,則接受string_view就足夠了。如果以后需要引用數據或需要修改數據,則可以使用string(my_string_view)顯式轉換為C ++字符串對象。
將string_view添加到現有代碼庫中并非總是正確的答案:更改參數以通過string_view傳遞可能效率不高,如果這些參數隨后傳遞給需要字符串或以NUL終止的const char *的函數。最好從實用程序代碼開始向上使用string_view,或者在啟動新項目時保持完全一致。
其他事項
- 與其他字符串類型不同,你應該按值傳遞string_view,就像int或double一樣,因為string_view是一個很小的值。
- string_view不一定是NUL終止的。因此,編寫以下內容并不安全:
以下這篇文章很好說明了上例的不安全:
《C++ string_view 的坑》
但是,下面是好的代碼:
printf("%.*s\n", static_cast<int>(sv.size()), sv.data());- 你可以輸出string_view,就像輸出字符串或const char*一樣:
大多數情況下,你可以將接受const string&或NUL終止的const char*的現有例程安全的轉換為string_view。在執行此操作時遇到的唯一危險是,如果已獲取函數的地址,則將導致編譯中斷,因為生成的函數指針類型將有所不同。
總結
以上是生活随笔為你收集整理的string_View理解与用法(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 开发常用镜像资源替换为国内开源镜像(yu
- 下一篇: 关于项目延迟交付