TinyXML注意事項

使用TinyXml庫值得注意的幾個地方

 (2012-12-16 17:46:49)

 

 這兩天仔細看了下TinyXml的源代碼, 完美地搞清楚了一些網友和我自己的很多疑問. 鑑於TinyXml的實用性, 而且現在不少人在使用, 就決定在此做點有意義的事情 ---- 列出使用TinyXml庫值得注意的幾個地方.

     關於TinyXml庫的介紹網上有很多資料, 大家可以試着搜下, 這裏我就不多說了, TinyXml很小巧, 但它提供了非常豐富的接口, 特別適用於存取程序的數據, 如果你使用它, 相信你會感覺到它的靈活的大笑.

     TinyXml下載地址: http://download.csdn.net/detail/hoyt00/3904805

                                http://sourceforge.net/projects/tinyxml/

 

     以下幾點值得注意哦:

 

1.new出指針而不見delete.

     如果你看了TinyXml的文檔或者一些網友寫的例子程序, 你會發現, 其中new出了很多對象而不見一個delete, 這個問題我當時也感覺特別難以接受, 網上也有各種各樣的說法, 有說"nwe出這麼多, 內存不泄漏纔怪", 有說"TinyXml的指針有自銷燬功能"等等, 這些說法都是錯誤的, 只要你正確的使用TinyXml, 無論你new多少(當然在內存的有效使用範圍之內)都不會有內存泄漏, 而且TinyXml的指針根本沒有自銷燬功能, 可以想像一下, 在C++中一個沒有經過任何類的包裝的指向堆內存的指針變量怎麼可能在它自身的生命結束時管得了它所指向的對象? 那它到底是怎麼回事?
     我們都知道(這裏我假設你已經看過TinyXml文檔), TinyXml是利用DOM(文檔對象模型)來呈現XML文檔的, 在它眼中XML文檔就是一棵樹, TiXmlDocument對象就是這棵樹的根結點, 在一個完整的文檔中, 除了它, 其餘結點必須都是它的後代, 所以TinyXml用了一個很巧妙的方法來析構每一個結點所對應的對象 ---- 每個結點的析構任務都委託給了它的父親, 這樣只要保證父親被正確析構, 或者調用了父親的Clear函數, 它的所有後代都會被正確的析構, 所以對整個文檔來說只要TiXmlDocument對象被正確析構, 那就萬無一失了, 這棵樹會先從葉子銷燬, 一直到樹根. 像這種數據結構, 爲了保證樹的完整性而使用堆內存, 和由它自己管理內存也是理所當然的, 由此便可得到以下幾個結論:
     (1)TiXmlDocument對象最好在棧上創建, 如果在堆上創建了, 那你必須得自己銷燬它, 千萬不要像別的對象一樣new出了就不管了.
     (2)除了TiXmlDocument對象, 樹中的別的結點對象, 必須是堆上創建的, 千萬不要把棧上對象的地址鏈接(LinkEndChild)到樹中, 因爲棧上對象是不能用delete銷燬的, 當然TinyXml也有對棧上對象插入的方法, 以下會說到.
     (3)除了文檔結點, new出的所有結點對象必須被鏈接到一個父親結點上, 纔可以不用管對象的delete, 而且必須不能管, 不然有可能整棵樹會被破壞而得不到正確的遍歷和析構, 如果new出的對象從來沒鏈接到某棵樹上, 而且將來也不打算鏈接, 無論有沒有別的結點鏈接到它身上, 你都必須手動delete它.
     (4)不要嘗試鏈接已經被別的結點鏈接過的指針, 很顯然, 這會造成無法估量的麻煩, 不用多說, 大家都懂的.

 

2.Insert vs. Link.

     當然不是所有人都喜歡new, 可能他們更喜歡像std::vector那樣插入元素的副本而不是交給它一個指針, TinyXml也提供了同樣的方式插入結點 ---- Insert函數(InsertEndChild, InsertBeforeChild, InsertAfterChild), 因爲這些函數插入的是結點的副本(包括所有子結點) ,所以可以傳給它棧上或堆上的對象, 但是要注意, 如果傳入的是堆上對象, 那還必須手動delete它, 而不像LinkEndChild那樣鏈接了就不能管了, 因爲樹析構的時候析構的是它的副本, 而不是它. 我更傾向於使用Link + 堆上對象, 而不Insert + 棧上對象, 因爲這樣可以省去創建副本的運行成本, 除非要插入到具體的位置, 而不是End, 當然也可以修改源代碼使Link函數也有這種功能.

 

3.元素的屬性不是普通結點.

     與文本對象(TiXmlText)不同, 屬性對象(TiXmlAttribute)在文檔樹中不是以普通結點形式存在的, 也不從TiXmlNode類派生, 而是被元素結點的數據成員 ---- 一個TiXmlAttributeSet對象所管理, 這個TiXmlAttributeSet對象持有一個環狀的TiXmlAttribute對象鏈表(具體可以參看源代碼), 所以TiXmlElement對象通過調用Child系列函數是無法得到屬性的, 相反這件事是通過Attribute系列函數完成的. 訪問器(從TiXmlVisitor派生的用戶實現的類對象, 一種附加的回調機制, 具體參看源代碼)沒有針對TiXmlAttribute對象的接口, 而必須在對TiXmlElement對象的實現中處理屬性, 因此TiXmlAttribute類也沒有Accept虛函數.

 

4.儘量不要在xml中使用中文.

     TinyXml只認識UTF-8和ISO 8859-1編碼, 而不知GB2312爲何物, 但事實上你以GB2312在文檔中寫入中文, 之後可以正確讀取, 而且文檔在記事本中打開也能顯示正確的中文, 其實這是種巧合, 並不是TinyXml支持GB2312了.
     這個問題需要解釋下, 我已經仔細分析過了, TinyXml的函數有char*類型參數, 而沒有wchar_t*類型參數, 所以直接在程序中向文檔寫入中文必然是GB2312方式(這裏是以VC編譯器爲例的), 這時char*只是指向一塊內存塊, 跟void*一樣, 這塊內存只有用GB2312才能正確解釋爲中文, 因爲TinyXml是被設計跨平臺的, 所以不要指望它會調用WideCharToMultiByte和MultiByteToWideChar來幫你做轉換, 而以GB2312寫入的中文在讀取時這些中文字符的碼值是不變的, 也就是你在準備寫入文檔時是碼值是多少, 讀取到程序裏的值就是多少, 而把這個值當作GB2312編碼時就是原來寫文檔時的中文字符了, 當把寫入的文檔在記事本打開時, 由於沒有utf-8標記字節0xEF 0xBB 0xBF(TinyXml默認不寫入這三個字節, 稍後再說怎麼讓它寫入), 所以記事本把xml文件當作GB2312編碼打開, 就陰差陽錯地把原本錯誤的字節以正確的中文字符顯示了, 但終究這篇xml文檔是utf-8編碼的, 那些中文字符本應顯示爲亂碼的, 就這樣錯誤的寫入, 錯誤的讀取, 記事本錯誤的判斷, 都加到一起就離奇地沒有錯誤了.
     但錯誤還是錯誤, 有一個辦法, 就是在文檔頭部加上utf-8標記字節0xEF 0xBB 0xBF, 這樣記事本就能正確判斷文檔編碼, 正確地以utf-8打開, 正確地把中文顯示爲亂碼.
     基於這幾種錯誤疊加的現象, 如果你生成的xml文檔只用在你自己特定的程序中 而不用在其它軟件(比如有時要用別的文本處理軟件打開查看內容), 那麼在文檔中存取中文是完全沒有問題的, 但在別的地方以utf-8打開時中文就是亂碼.
     保證所有地方都正確的方法是在寫入時和讀取時用WideCharToMultiByte和MultiByteToWideChar把GB2312編碼的中文字串轉換爲UTF-8編碼的中文字串, 如此, 所有軟件都能正確的讀取UTF-8編碼的中文字符(爲了讓記事本正確的判斷爲UTF-8, 可以加上utf-8標記字節, 雖然它不是標準, 但普遍使用).當然還是那句話 ---- 儘量不使用中文和其它非英文字符, 除非迫不得已.

 

 

另外我在看源代碼時, 有幾個地方也發表下自己的想法:

 1.關於explicit關鍵字

     tinystr.h第85行  TIXML_EXPLICIT TiXmlString ( const char * str, size_type len) : rep_(0)

     (TIXML_EXPLICIT宏的內容是explicit) explicit關鍵字的作用是使單參數的構造函數不用作隱式轉換, 但這個構造函數有兩個參數, 很顯然, 它根本就沒有發生隱式轉換的可能, 也完全沒有必要使用explicit關鍵字.


2.關於成員函數const和非const

     TinyXml的類庫中有許多類的函數有const和非const版本, 爲什麼非const版本的函數不把函數體重新寫一遍而是用const_cast把const函數的const性質轉換掉再調用它呢? 其實函數體也就返回數據成員的那一兩句而已.


3.關於默認實參

     有些類的重載成員函數們, 一個函數的參數表和另一個函數的參數表的前面部分完全一致, 爲什麼不使用默認實參而是參數較少的函數版本調用參數較多的函數版本呢?

 

4.關於Test項目中的亂碼

     這也是很多人都疑惑的一個問題 ---- TinyXml庫自帶Test項目竟然編譯不過, 這個問題的原因很簡單, 打開xmltest.cpp文件, 定位到第1141行, 你會發現, 該行和下面幾行有亂碼, 這又是怎麼回事?

     其實這幾行是作者爲測試ISO 8859-1編碼(一種單字節編碼, 編碼範圍使用了8位的所有取值, 也就是0到0xFF)文檔而寫, 意圖把ISO 8859-1字符串寫入文檔對象, 再從文檔對象讀取, 只有用ISO 8859-1編碼才能正確解釋.

     但是編譯器以GB2312打開cpp文件時遇到這幾個字符就發生了奇怪的事情 ---- 源碼變亂碼了. 本來亂碼沒什麼的, 大不了我輸出到xml文件也亂碼就是了, 但問題是那幾個字符不僅使它本身亂, 而且它後面的一個asc2字符也有50%的機率跟着遭殃, 因爲我們都知道用GB2312解碼字符串時如果第一個字節大於0x7F, 一般情況下會把它和接下來的一個字節當作一個整體字符, 當然這個字符可能顯示, 也可能不顯示, 無論怎樣都不是正確的, 所以你會看到那些字串中有用於xml元素的"<"符號而沒有">"符號, 有的字串甚至都沒反引號, 這時不要簡單的加上反引號, 雖然語法正確了, 能編譯, 但xml語法還沒糾正, 調試時會導致文檔對象裏的斷言失敗而退出. 另外我還查看了這幾個亂碼字符的二進制值:



轉自:

http://blog.sina.com.cn/s/blog_6f7b73770101aq06.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章