STL 的string類怎么啦
STL 的string類怎么啦?陳皓
前言
上個(gè)周末在和我的同學(xué)爬香山閑聊時(shí),同學(xué)說(shuō)到STL中的string類曾經(jīng)讓他備受折磨,幾年前他開(kāi)發(fā)一個(gè)系統(tǒng)前對(duì)string類還比較清楚,然后隨著程序的復(fù)雜度的加深,到了后期,他幾乎對(duì)string類失去了信心和信任,他覺(jué)得他對(duì)string類一頭霧水。老實(shí)說(shuō),我?guī)啄昵耙灿型瑯拥耐纯啵ň褪钱?dāng)我寫下《標(biāo)準(zhǔn)C++類string的Copy-On-Write技術(shù)》之前的一段時(shí)間)。那時(shí),我不得不研究那根本不是給人看的SGI出品的string類的源碼,代碼的可讀性幾乎為零,而且隨著了解越深入,就越覺(jué)得C++的世界中到處都是陷阱和缺陷。越來(lái)越覺(jué)得有時(shí)候那些類并不像自己所想象的那樣工作。
為什么會(huì)發(fā)生這樣的情況呢?string類只是一個(gè)“簡(jiǎn)單”的類,如果是一些比較復(fù)雜的類呢?而這幾年來(lái),C++陣營(yíng)聲討標(biāo)準(zhǔn)模板庫(kù)中的標(biāo)準(zhǔn)string類愈演愈烈。C++陣營(yíng)對(duì)這個(gè)“小子”的爭(zhēng)討就沒(méi)有停止過(guò)。相信在下一個(gè)C++的標(biāo)準(zhǔn)出臺(tái)時(shí),string類會(huì)有一個(gè)大的變化。
了解string類
在我們研究string類犯了什么毛病之前,還讓我先說(shuō)一下如何了解一個(gè)C++的類。我們要了解一個(gè)C++的類,一般來(lái)說(shuō),要從三個(gè)方面入手。
一、意圖(Intention)。知其然還要知所以然,string類的意圖是什么?只有了解了意圖,才知道它的思路。這是了解一個(gè)事物最重要最根本的部分。不然,你會(huì)發(fā)現(xiàn)它的行為并不會(huì)像你所期望的那樣。string類的意義有兩個(gè),第一個(gè)是為了處理char類型的數(shù)組,并封裝了標(biāo)準(zhǔn)C中的一些字符串處理的函數(shù)。而當(dāng)string類進(jìn)入了C++標(biāo)準(zhǔn)后,它的第二個(gè)意義就是一個(gè)容器。這兩件事并不矛盾,我們要需理解string的機(jī)制,需要從這兩個(gè)方面考慮。
二、規(guī)格(Specification)。我們注意到string類有太多的接口函數(shù)。這是目前C++陣營(yíng)中聲討其最重的話題。一個(gè)小小的string類居然有106個(gè)成員接口函數(shù)。居然,C++標(biāo)準(zhǔn)委員會(huì)會(huì)容忍這種“ugly”的事情的發(fā)生?目前的認(rèn)為導(dǎo)致“C++標(biāo)準(zhǔn)委員會(huì)腦子進(jìn)水”的主流原因有兩點(diǎn),一個(gè)是為了提高效率,另一個(gè)是為了常用的操作。
1)讓我們先來(lái)看效率,看看string類中的“==”操作符重載接口:
bool operator==(const string& lhs, const string& rhs);
bool operator==(const string& lhs, const char* rhs);
bool operator==(const char* lhs, const string& rhs);
頭一個(gè)很標(biāo)準(zhǔn),而后兩個(gè)似乎就顯得沒(méi)有必要了。如果我們調(diào)用:(Str == “string”)如果沒(méi)有后面兩個(gè)接口,string的構(gòu)造函數(shù)會(huì)把char*的 ”string”轉(zhuǎn)成string對(duì)象,然后再調(diào)用第一個(gè)接口,也就是 operator==(str, string(“string”))。如此“多余”的設(shè)計(jì)只能說(shuō)是為了追求效率,為了省去調(diào)用構(gòu)造/析構(gòu)函數(shù)和分配/釋放內(nèi)存的時(shí)間(這會(huì)節(jié)省很多的時(shí)間)。在后面兩個(gè)接口中,直接使用了C的strcmp函數(shù)。如此看來(lái),這點(diǎn)設(shè)計(jì)還是很有必要的。string類中有很多為了追求效率的算法和設(shè)計(jì),比如:Copy-on-Write(參看我的《標(biāo)準(zhǔn)C++類string的Copy-On-Write技術(shù)》)等。這些東西讓我們的string變得很有效率,但也帶來(lái)了陷阱。如果不知道這些東西,那么當(dāng)你使用它的時(shí)候發(fā)生不可意料的問(wèn)題,就會(huì)讓你感到迷茫和不知所措。
2)另一個(gè)讓string類擁有這么龐大的接口的原因是常用的操作。比如string類的substr(),這是一個(gè)截取子字符串的函數(shù)。其實(shí)這個(gè)函數(shù)并不需要,因?yàn)閟tring有一個(gè)構(gòu)造函數(shù)可以從別的string類中指定其起始和長(zhǎng)度構(gòu)造自己,從而實(shí)現(xiàn)這一功能。還有就是copy()函數(shù),這也是一個(gè)沒(méi)有必要的函數(shù),copy這個(gè)函數(shù)把string類中的內(nèi)容拷貝到一個(gè)內(nèi)存buffer中,這個(gè)方法實(shí)踐證明很少有人使用。可能,1)為了安全起見(jiàn),需要有這樣一個(gè)成員把內(nèi)容復(fù)制出去;2)設(shè)計(jì)者覺(jué)得copy要比strcpy或是memcpy好寫也漂亮很多吧。copy()比起substr()更沒(méi)有必要存在。
三、實(shí)現(xiàn)(Implementation)。C++標(biāo)準(zhǔn)并沒(méi)有過(guò)多的干預(yù)實(shí)現(xiàn)。不同的產(chǎn)商會(huì)有不同的實(shí)現(xiàn)。不同的產(chǎn)商會(huì)考慮標(biāo)準(zhǔn)中的一件事情是否符合市場(chǎng)的需要,并要考慮自己的編譯器是否有能夠編譯標(biāo)準(zhǔn)的功能。于是,他們總是會(huì)輕微或是顛覆地修改著標(biāo)準(zhǔn)。C++在編譯器的差異是令人痛苦和絕望的,如果不了解具體的實(shí)現(xiàn),在你使用C++的時(shí)候,你也會(huì)發(fā)現(xiàn)它并不像你所想象的那樣工作。
只有從上述三個(gè)方面入手,你才能真正了解一個(gè)C++類,而你也才能用好C++。C++高手們都是從這樣的三個(gè)方面剖析著C++現(xiàn)實(shí)中的各種類,并以此來(lái)驗(yàn)證C++類的設(shè)計(jì)。
String類犯了什么錯(cuò)?
string類其實(shí)挺好的。它的設(shè)計(jì)很有技術(shù)含量。它有高的效率、運(yùn)行速度快、容易使用。它有很充足的接口可以滿足各式各樣的法,使用起來(lái)也很靈活。
然而,這個(gè)string類似乎有點(diǎn)沒(méi)有與時(shí)俱進(jìn),它現(xiàn)在的設(shè)計(jì)還保持著10年以前的樣子,10年來(lái),整個(gè)技術(shù)環(huán)境都出現(xiàn)很多變革,人們也在使用C++的過(guò)程中得到了許許多的經(jīng)驗(yàn)。比如:現(xiàn)在的幾乎所有的程序都運(yùn)行在一個(gè)多進(jìn)/線程的環(huán)境中,而10年前主流還只是單進(jìn)/線程的應(yīng)用,這是一個(gè)巨大的變化。近幾年來(lái),C++陣營(yíng)也在實(shí)踐中取得了很多的經(jīng)驗(yàn),特別是模板技術(shù),而我們的STL顯然沒(méi)能跟上腳步。
首當(dāng)其沖的是string類,目前C++陣營(yíng)對(duì)string類的聲討主要集中在下面幾個(gè)方面。對(duì)于下面的這些問(wèn)題,C++陣營(yíng)還是爭(zhēng)論不休。不過(guò),作為一個(gè)好的程序員,我們應(yīng)該在我們的設(shè)計(jì)和編程中注意一下這些方面。
1)目前的標(biāo)string類有106個(gè)接口函數(shù)(包括構(gòu)造和析構(gòu)函數(shù)),如果考慮上默認(rèn)參數(shù),那么就一共有134不同的接口。其中有5個(gè)函數(shù)模板還會(huì)產(chǎn)生無(wú)窮多個(gè)各種各樣的函數(shù)。還有各種各樣的性能上的優(yōu)化。在這么從多的成員函數(shù)中,很多都是冗余不必要的。最糟糕的是,眾多程序員們并不了解它們,導(dǎo)到要么浪費(fèi)了它的優(yōu)勢(shì),要么踩中了其中的陷阱。
2)很多人認(rèn)為string類提供的功能中,該有的沒(méi)有,已有的又很冗余。string類在同一個(gè)功能上實(shí)現(xiàn)了多次,而有一些功能卻沒(méi)有實(shí)現(xiàn)。如:大小寫不區(qū)分的比較,寬行字符(w_char)的支持,和字符char型數(shù)據(jù)的接口等等。
3)作為一個(gè)STL的容器,string類和的設(shè)計(jì)和其它容器基本一樣。這些STL都不鼓勵(lì)被繼承。因?yàn)镾TL容器的設(shè)計(jì)者們的幾乎都遺忘了虛函數(shù)的使用,這樣阻止了多態(tài)性,也許,這也是一個(gè)為了考慮效率和性能的設(shè)計(jì)。對(duì)于STL容易這樣的設(shè)計(jì),大多數(shù)人表示接受。但對(duì)于string類,很多人認(rèn)為,string類是一個(gè)特殊的類,考慮到它被使用的頻率,string類應(yīng)該得到特殊的照顧。
4)還有很多人認(rèn)為標(biāo)準(zhǔn)的strng類強(qiáng)行進(jìn)行動(dòng)態(tài)內(nèi)存分配(malloc),那怕是一個(gè)很短的字符串。這會(huì)導(dǎo)致內(nèi)存碎片問(wèn)題(我們知道內(nèi)存碎片問(wèn)題會(huì)讓malloc很長(zhǎng)時(shí)間才能返回,由于降低了整個(gè)程序的性能。而關(guān)于內(nèi)存碎片問(wèn)題,這是一個(gè)很嚴(yán)重的問(wèn)題,目前除了重啟應(yīng)用程序,還沒(méi)有什么好的方法)。他們認(rèn)為,string類的設(shè)計(jì)加劇了內(nèi)存碎片問(wèn)題。他們希望string類能夠擁有自己的棧上內(nèi)存來(lái)存放一些短字符串,而不需要總是去堆上分配內(nèi)存。(因?yàn)橛糜趕tring類的字符串長(zhǎng)度幾乎都不會(huì)很長(zhǎng))
5)很多string類的實(shí)現(xiàn),都采用了Copy-On-Write(COW)技術(shù)。雖然這是一個(gè)很有效率的技術(shù)。但是因?yàn)閮?nèi)存的共享,導(dǎo)致了程序在“多線程”環(huán)境中及容易發(fā)生錯(cuò)誤,如果分別在兩個(gè)線程中的string實(shí)例共享著同一塊內(nèi)存,很有可能發(fā)生潛在的內(nèi)存問(wèn)題(參看我的《標(biāo)準(zhǔn)C++類string的Copy-On-Write技術(shù)》最后一節(jié)示例)。目前這一技術(shù)很有可能從下一版本的標(biāo)準(zhǔn)中移去。(而一些新版本的STL都不支持COW了,如Microsoft VC8.0下的STL)
6)標(biāo)準(zhǔn)的string類不支持policy-base技術(shù)的錯(cuò)誤處理。string遇到錯(cuò)誤時(shí),只是簡(jiǎn)單地拋出異常。雖然這是一個(gè)標(biāo)準(zhǔn),但有一些情況下不會(huì)使用異常(GCC –fno-exception)。另外,不可能要求所有的程序都要在使用string操作的時(shí)候try catch,一個(gè)比較好的方法是string類封裝有自己的error-handling函數(shù),并且可以讓用戶來(lái)定義需要使用哪一種錯(cuò)誤處理機(jī)制。
由于string類的的種種不如人意,特別是106個(gè)接口函數(shù)讓許多人難以接受,有很多人都在寫適合自己的string類。但總體來(lái)說(shuō),標(biāo)準(zhǔn)的string類是一個(gè)好壞難辨的類。無(wú)論你是否是一個(gè)高級(jí)咨深的程序員,你都會(huì)用到它。它和整個(gè)C++一樣,都是一把雙刃劍,在大多數(shù)情況下,它還是值得我們信賴。
目前,對(duì)string類的爭(zhēng)論有很多很多。C++標(biāo)準(zhǔn)委員會(huì)也說(shuō):“The C++ Standard is the best we could make it. If we could have agreed on how to make it better, then we would have made it better.”在C++這么一個(gè)混亂的領(lǐng)地,雖然STL并不完美,但對(duì)比起來(lái)說(shuō),他還是顯得那么地漂亮和精致辭。
后記
又到了關(guān)于C++文章結(jié)束的時(shí)候,我還是要老調(diào)重彈。C++這個(gè)世界是很復(fù)雜很危險(xiǎn)的。單單本文章中的一個(gè)小小的string類就能引起這么多的討論,何況是別的類?
讓我們大膽地懷疑一下, C++是否真是一種高級(jí)的語(yǔ)言?目前的C++需要使用他的人有相當(dāng)?shù)膶I(yè)技術(shù)水平,而寄希望于人的高水平看來(lái)是和技術(shù)的發(fā)展背道而馳的。好的一門開(kāi)發(fā)語(yǔ)言是讓人可以容易方便地把更多的精力集中在業(yè)務(wù)邏輯性上。而C++這門語(yǔ)言卻需要技術(shù)人員比以住的C有著更為高深和專業(yè)的技術(shù)知識(shí)和能力。
是我們對(duì)string的“無(wú)知”搞亂了我們的程序,還是string這個(gè)類的設(shè)計(jì)把我們的程序搞亂了?是C++自己把軟件開(kāi)發(fā)搞得一團(tuán)混亂?還是使用C++的人把其搞得一團(tuán)混亂?好像兼而有之,目前我們無(wú)法定論,但我們知道,無(wú)論是C++標(biāo)準(zhǔn)委員會(huì),還是開(kāi)發(fā)人員,大家的C++未來(lái)之路都還有很長(zhǎng)。
(轉(zhuǎn)載時(shí)請(qǐng)注明作者和出處。未經(jīng)許可,請(qǐng)勿用于商業(yè)用途) 更多文章請(qǐng)?jiān)L問(wèn)我的Blog: http://blog.csdn.net/haoel
相關(guān)文章
opencv實(shí)現(xiàn)角點(diǎn)檢測(cè)
這篇文章主要為大家詳細(xì)介紹了opencv實(shí)現(xiàn)角點(diǎn)檢測(cè),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08新手小心:c語(yǔ)言中強(qiáng)符號(hào)與弱符號(hào)的使用
本篇文章適合新手。是對(duì)c語(yǔ)言中強(qiáng)符號(hào)與弱符號(hào)的使用進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05Qt鍵盤事件實(shí)現(xiàn)圖片在窗口上下左右移動(dòng)
這篇文章主要為大家詳細(xì)介紹了Qt鍵盤事件實(shí)現(xiàn)圖片在窗口上下左右移動(dòng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08C語(yǔ)言模擬實(shí)現(xiàn)學(xué)生學(xué)籍管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言模擬實(shí)現(xiàn)學(xué)生學(xué)籍管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-07-07使用C++實(shí)現(xiàn)MySQL數(shù)據(jù)庫(kù)連接池
這篇文章主要為大家詳細(xì)介紹了如何使用C++實(shí)現(xiàn)MySQL數(shù)據(jù)庫(kù)連接池,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,有需要的小伙伴可以了解下2024-03-03