C++ 自己寫一個更好的 string 需要什麼步驟?
有一個問題是「為什麼c++庫都要自己寫一個string」
裡面的回答基本把string批判的一無是處那麼請問如果想寫一個比原生更好的string需要怎麼設計呢?
Rust Book 講到 String 的時候,就提到過 String 的一部分設計難點:
Bytes and Scalar Values and Grapheme Clusters! Oh my!
This leads to another point about UTF-8: there are really three relevant ways to look at strings, from Rust』s perspective: as bytes, scalar values, and grapheme clusters (the closest thing to what people would call letters).If we look at the Hindi word 「??????」 written in the Devanagari script, it is ultimately stored as a Vecof u8 values that looks like this:[224, 164, 168, 224, 164, 174, 224, 164, 184, 224, 165, 141, 224, 164, 164,224, 165, 135]That』s 18 bytes, and is how computers ultimately store this data. If we look at them as Unicode scalar values, which are what Rust』s char type is, those bytes look like this:["?", "?", "?", "?", "?", "?"]There are six char values here, but the fourth and sixth are not letters, they』re diacritics that don』t make sense on their own. Finally, if we look at them as grapheme clusters, we』d get what a person would call the four letters that make up this word: ["?", "?", "??", "??"]
Rust provides different ways of interpreting the raw string data that computers store so that each program can choose the interpretation it needs, no matter what human language the data is in.A final reason Rust does not allow you to index into a String to get a character is that indexing operations are expected to always take constant time (O(1)). It isn』t possible to guarantee that performance with a String, though, since Rust would have to walk through the contents from the beginning to the index to determine how many valid characters there were.
只截取了跟 Unicode 有關的一部分,其他關於所有權什麼的可以自己點進鏈接看。
一個不止處理ASCII的String庫,起碼會處理NFC/NFD之類的問題。
至於只讀字元串(比如防止多份拷貝然後析構了一個導致其他GG)什麼的,可以參考 "string_view"sv (gcc 7.x 和 clang 4.x 開始不需要加 experimental)
正如 Rust Book 最後一章所說,Strings are Not so Simple。把 string 設計成 char array 並不是不可行,但是它的 tradeoff 就是理解起來很容易,遇到非ascii就瞎了。string 這種庫,理應比一般的庫複雜,而且和語言自己的資源管理方案息息相關,要求對語言本身理解得足夠好。所以一般水平,要求又高,還是用QString之類的吧。C++/Rust 就是那種對庫作者水平要求比較高的語言。
String 本身固有的複雜度,和 C++ 資源管理複雜度,兩件讓人頭疼的事卷積起來,那就非常的不開心了。
To summarize, strings are complicated. Different programming languages make different choices about how to present this complexity to the programmer. Rust has chosen to make the correct handling of String data the default behavior for all Rust programs, which does mean programmers have to put more thought into handling UTF-8 data upfront. This tradeoff exposes more of the complexity of strings than other programming languages do, but this will prevent you from having to handle errors involving non-ASCII characters later in your development lifecycle.首先好好看下 Unicode 到底意味著什麼。
儘管你可以用 char、char32_t 等等作為底層實現,但要注意你要實現的是「字元串」而不是「char 串」、「UTF-8 串」、「UTF-32 串」、「Unicode 碼位串」。
看到很多回答都提到把 UTF-32 編碼單元當作字元,且不提 UTF-32 編碼單元只是目前與 Unicode 碼位一一對應,Unicode 碼位本身與字元也並不是一一對應的。比如「ò」和「o?」是同一個字元,但是前者只佔一個 Unicode 碼位(U+00F2),後者則是由兩個字元拼接而成的(一個普通的「o」(U+006F)和一個特殊的組合字元(U+0300)。光這一點就能引出子串、查找、歸一化等問題。
Unicode 里還有一些奇怪的機制(比如很多為了奇怪語種搞出的特性,還有比如為了支持國旗以及政治正確 emoji 搞出來的幺蛾子)。如果你想實現一個「更好」的字元串類,在動手之前好好研究下應該是必要的。
我覺得std::string幾乎是最好的通用設計了
欽點編碼這是玩不轉的,編碼不止常用的,那些GBK系列咋辦?
你不管以前數據的兼容?
變長字符集和非變長的很多介面的複雜度都會不同
不可變的話,內存和效率損失,上層反覆調用某些借口就GG了
既然說通用,我建議搞一個text,處理實際你們的文本編碼等各種問題
說到底,C++程序員就得為特殊的需求反覆造輪子,逃不過的宿命23333
字元串不是關鍵,關鍵是你要提供完善的處理字元串的方法。從最簡單的流讀入,到處理編碼之類的,還有正則等等,這些操作沒有就太難受了
別把string設計成字元的容器,不論是數組還是序列。理由不僅是encoding,更重要的是string作為容器的結構並不符合它的普遍應用場景:讀取和連接。我們多久才會用到一次修改內容甚至是in-place sorting?
string_view是另一層套在已有系統上的hack solution,如果說string應該怎麼設計,我傾向於string就應該是現在的string_view。
string應當對編譯時字元串做優化:有極其多字元串本質上就是編譯時定義的,而且終其一生不會改變。
string應當對連接進行優化:用鏈表連接已有的靜態和動態字元串,並有選擇地對小字元串動態歸併進small string optimization buffer。而不是:為了數據連續性不斷進行內存分配和拷貝。寫一點想到的:1. 做成編碼相關的,或者乾脆欽定UTF-16
2. 提供更多方便的方法(比如split,format,toUpper,等等……)
想到再寫把字元串變成只讀,修改函數全部做成返回一個新字元串,然後強制讓自己認識到字元串操作基本很難成為瓶頸,不要為了一丁點心理上的舒服,改回可修改字元串。
順手做一個兼容的regex,然後大量使用regex,強制讓自己認識到,一個差不多靠譜的regex也基本很難成為瓶頸,不要為了一丁點心理上的舒服,而去特化各種操作函數。
至於ansi、utf8、utf32和utf16,用模板做出4個類就行了。如果一定要統一成一個字元串,只在IO的時候編碼解碼,那就使用utf32字元。原因很簡單,operator[]好寫不說,utf32到其他各種編碼的轉換都很容易。
/* 對於C++程序來說,大部分人還是在new各種其他類型的時候把性能拉下去的,做個池可破。過了這一關再來想別的。 */
1.別依賴char_traits
2.少聲明成員函數,別過度使用成員模板,能聲明成
int compare(basic_string_view&
就不要寫成
int compare(basic_string_view&
int compare(size_type pos1, size_type n1, basic_string_view&
template&
int compare(size_type pos1, size_type n1, const T t,
size_type pos2, size_type n2 = npos) const;
int compare(const basic_string str) const noexcept;
int compare(size_type pos1, size_type n1, const basic_string str) const;
int compare(size_type pos1, size_type n1, const basic_string str,
size_type pos2, size_type n2 = npos) const;
int compare(const charT* s) const;
int compare(size_type pos1, size_type n1, const charT* s) const;
int compare(size_type pos1, size_type n1, const charT* s, size_type n2) const;
更不要搞成
template&
int compare(const T t) const;
template&
int compare(size_type pos1, size_type n1, const T t) const;
template&
int compare(size_type pos1, size_type n1, const T t,
size_type pos2, size_type n2 = npos) const;
int compare(const basic_string str) const noexcept;
int compare(size_type pos1, size_type n1, const basic_string str) const;
int compare(size_type pos1, size_type n1, const basic_string str,
size_type pos2, size_type n2 = npos) const;
int compare(const charT* s) const;
int compare(size_type pos1, size_type n1, const charT* s) const;
int compare(size_type pos1, size_type n1, const charT* s, size_type n2) const;
3.提供個返回string_view的substr/slice成員函數
說下個人的 YY 。
這裡的 view/mut_view 是給全部完整對象類型的。char_array 給字元類型,coded_*** 由上述東西與 code_traits 組合。
函數能用 view 的就用 view ,刪了複製構造函數,改用 view 做中介。以免搞出複製障礙、重載之類的破事。
有的場合酌情考慮用 mut_view 。view 和 char_array 不要 Traits 。char_array 幾乎就是是去掉 Trairs 的「純粹」 std::basic_string ,盡量考慮用一堆編碼無關的介面。
去掉雞肋的 char_traits ,換用 code_traits 。
再加上 coded_(mut_)view 和 coded_string 這些分別給 (mut_)view 和 char_array 套上編碼特性的類。coded_string 里搞編碼轉換/to_upper/驗證合法之類的。對 UTF 之間轉換特化下,其他以 native_code 為中介轉換。
char_array 是容器,而 coded_string 不是, coded_string 原則上不能單字元修改,沒有 operator[] 。兩者間需要一些顯式轉換。
最後用的字元串類就是 coded_string 和 char_array ,兩者分別處理 code points 和 code units 。當然 coded_view 和 view 可能更常用。
進一步的要求可以在上面的設施中上做文章,譬如拿 bidirect_vec&
或許可以拿 shared_ptr 搞個 shared_view ,然後用 bidirect_vec&
最重要的就是要把握一個原則——string不是char的容器。
從樓上這麼多站在自己的立場 自己的主要用途 自己的看法來提的建議來看,我看樓主還是學學樓上這些人:」寫一個僅合適自己的應用場景的string「就夠了。。沒看到大家都特別自私的嗎,
我是同意那句話的。
如果放在通用的場景下,很難做到。平衡各方的需求非常難,會變成一個性能蹺蹺板。
也就是說沒有特殊要求的情況下,原生的是最好的,或者說性價比最高。但是在特定庫的場景下,自己寫一個實現,優化80%的操作非常有可能。先寫一個memcpy試試,先寫出一個性能最高的版本出來再說
很naive,不對答案的錯誤造成的讀者的認知偏差負責,以下所有句子默認省略「我認為」三個字
std::string 的設計思想和 STL 以及整個 Std Librarys 是一致的,不以運行效率為代價做任何易用性上的改變,對於一些非常複雜或者沒有統一方案的東西寧可不加入,保持整體上的潔凈。
CppRef 上這樣描述 std::basic_string :
The class is dependent neither on the character type nor on the nature of operations on that type.
但是這樣的結果是大家都以其他語言的 string 去噴 std::string,就是因為都起了相同的名字——叫string。
那麼大家心目中的 string 在c++里是怎麼樣的呢 , text_view [1] 提案里這樣說的:
C++11 added support for new character types [N2249] and Unicode string literals [N2442], but neither C++11, nor more recent standards have provided means of efficiently and conveniently enumerating code points in Unicode or legacy encodings. While it is possible to implement such enumeration using interfaces provided in the standard &
and & libraries, doing so is awkward, requires that text be provided as pointers to contiguous memory, and inefficent due to virtual function call overhead.
Text_view [1] 是怎麼樣的呢:
(Text_view) provides iterator and range based interfaces for encoding and decoding strings in a variety of character encodings. The interface is intended to support all modern and legacy character encodings, though implementations are expected to only provide support for a limited set of encodings.
(Text_view) intended to solve the above (shortage in processing strings with &) issues while also providing a modern interface that is intuitive to use and can be used with other standard provided facilities; in particular, the C++ standard & library.
那麼要在C++中寫一個「更好的「的 string ,應該就是寫一個切合大家心中對 string 的所有易用性上的幻想的 string,大概可以參考 text_view [2] , Qt / QString , 既然要易用,配套個正則也是加分項吧(
[1]: Text_view: A C++ concepts and range based character encoding and code point enumeration library
[2]: Github : tahonermann/text_view
template&
typename Alloc = std::allocator&
using just_use_this_fucking_string = std::basic_string&
可以參考下QT的QString,甚至是c#的String,這些設計考慮很全面,用戶體驗非常不錯。
建議參考QString
按java或c#的string實現一遍
我寫過。
第一步是看unicode 10文檔。。第二步是放棄。不建議自己寫一個 String 類。
首先,你的所謂「更好」的標準是什麼?別告訴我是為了沒人批評。。。說具體一點,不要泛泛而談
其次,你了解別人設計的優點和缺點是什麼嗎?
第三,如果你已經了解了缺點,在設計和使用的時候,繞開缺點不就可以了嗎,為啥一定要自己去造一個輪子
舉例:曾經遇到的 String 的麻煩,以及繞開或者解決的辦法:
跨線程使用問題---使用 char * 進行參數傳遞
介面中使用會出現問題---介面中使用 char * 或者 const char *
推薦閱讀: