柚子快報邀請碼778899分享:C++Json項(xiàng)目筆記
柚子快報邀請碼778899分享:C++Json項(xiàng)目筆記
Github源項(xiàng)目地址:TinyJson
本人倉庫地址(跟原版差別不大,只是在有疑惑或者有收獲的地方加上的注釋作為筆記)
文章目錄
類的提前聲明為什么定義函數(shù)的時候同時寫左值和右值作為傳參?解答
SFINAE機(jī)制(疑惑)指針類型可以被隱式轉(zhuǎn)換成bool類型能夠隱式轉(zhuǎn)換成bool類型的數(shù)據(jù)
為什么要同時有number_value和int_value?(疑惑)什么時候成員函數(shù)需要被聲明為靜態(tài)的?(疑惑)size_t和std::string::size_type還是有區(qū)別為什么JsonValue的析構(gòu)函數(shù)不是純虛函數(shù)snprinf()Json數(shù)據(jù)中,”"“要進(jìn)行處理構(gòu)造函數(shù)非公有(public)(疑惑)解答:裝飾類,裝飾模式
為什么右值在傳值的時候還需要使用std::move(疑惑)數(shù)據(jù)一致性的實(shí)現(xiàn)匿名命名空間的作用
類的提前聲明
我覺得這個比較簡單,很好理解,因?yàn)樵贑語言中也有類似的語法:
int Temp(struct Exmp& value);
其中為什么要加上這個struct?就是為了告訴告訴編譯器:我這個類是存在的,只是它不在這個文件中,你先別報錯。
為什么定義函數(shù)的時候同時寫左值和右值作為傳參?
代碼如下:
Json(const object& values);
Json(object&& values);
右值不是可以用于初始化const左值引用嘛?為什么還需要單獨(dú)寫一個右值引用版本?
解答
其實(shí)可以不寫,因?yàn)橛抑悼梢赞D(zhuǎn)換成const左值引用,但是這會涉及到資源的所有權(quán)轉(zhuǎn)移(這里我也不是很了解,后面再補(bǔ)上吧),當(dāng)我們左值、右值情況都有相對應(yīng)的函數(shù)進(jìn)行處理的時候,一旦傳入是個右值,就優(yōu)先觸發(fā)移動拷貝構(gòu)造調(diào)用,使用移動語意,轉(zhuǎn)移資源,減少拷貝。
SFINAE機(jī)制(疑惑)
什么是SFINAE機(jī)制呢?這是一個縮寫,展開來即:Substitution Failure Is Not An Error(替換失敗不是錯誤),SFINAE是std::enable_if的模板別名。
[!ChatGPT] 在 C++ 模板編程中,SFINAE 是一種編譯技術(shù),它允許編譯器在模板參數(shù)推導(dǎo)或重載解析時忽略某些不符合條件的實(shí)例化選項(xiàng),而不會導(dǎo)致編譯錯誤。
代碼如下:
template
Json(const T& t) : Json(t.to_json()) {}
首先decltype應(yīng)該是見過的,和auto一樣用于自動類型推斷,class = dacltype(&T::to_json)就是想在類型T中找到那么個函數(shù)to_json,如果沒找到,就應(yīng)該換用別的構(gòu)造函數(shù),而應(yīng)該報錯;也就是說,模板中的第二個參數(shù)其實(shí)是對類型T的一個限定。
接下來我們再說說Json(t.to_json),由于對模板元編程不了解,一開始以為就是C++的顯式初始化列表,但是想起來沒有名為Json的成員變量,所以它的作用應(yīng)該是: 將t.to_json的返回值用于Json構(gòu)造函數(shù),但是具體是使用哪個重載函數(shù)就不一定了,看to_json的結(jié)果是什么。
這只是其中一段比較好理解的,還有根本看不懂的,等學(xué)了模板元編程再回來補(bǔ)吧:
template std::is_constructible && std::is_constructible int>::type = 0> Json(const M& m) : Json(object(m.begin(), m.end())) {} 指針類型可以被隱式轉(zhuǎn)換成bool類型 指針可以被隱式轉(zhuǎn)換成bool類型,這其實(shí)也好理解: 若是指針指向的是nullptr,則對應(yīng)的bool類型會是0; 否則就是1。 在Json中,有NULL這個類型,并且有對應(yīng)的Json構(gòu)造函數(shù),因此我們要避免出現(xiàn)異常,就需要禁用這種可能導(dǎo)致異常的隱式轉(zhuǎn)換: Json(void*) = delete; 能夠隱式轉(zhuǎn)換成bool類型的數(shù)據(jù) 數(shù)值類型。這種情況是最常見的吧,所有的數(shù)字0為false,非零值都被認(rèn)為是1,即為true指針類型。此處就屬于這個情況,就補(bǔ)過多贅述了指針和數(shù)值類型的比較。應(yīng)該也挺好理解吧 為什么要同時有number_value和int_value?(疑惑) 其中有兩個獲取NUMBER類型的值的函數(shù): /* * 在該json項(xiàng)目中 * 不會區(qū)分整數(shù)和非整數(shù) * number_value()和int_value() * 疑惑:為什么不能就使用一個number_value完成所有操作? */ double number_value(); int int_value(); 命名一個number_value()就能完成所有的任務(wù),為什么還需要后者呢? 原作者也說了,Json不區(qū)分NUMBER是整數(shù)還是非整數(shù)。 什么時候成員函數(shù)需要被聲明為靜態(tài)的?(疑惑) size_t和std::string::size_type還是有區(qū)別 我們都知道size_t是unsigned int類型,而std::string::size_type也是unsigned int(這是我在初學(xué)C++的時候,在《C++primer》中看到的,所以我一直以為這兩個就是同一個東西。 在大多數(shù)情況下,size_t和std::string::size_type是一個東西,因此它們通??梢曰Q,但是也可能因?yàn)槠脚_的不同而出現(xiàn)些許的差異。 因此,為了程序的可移植性,還是使用std::string::size_type會更好,可以避免很多不必要的問題。 總結(jié):最好還是使用C++本身就定義了的東西,特別是在編寫庫的時候,因?yàn)椴恢莱绦驎谑裁聪到y(tǒng)中運(yùn)行,因此,程序的可移植性尤為重要。 為什么JsonValue的析構(gòu)函數(shù)不是純虛函數(shù) class JsonValue{ protected: // 為什么使用的是友元? friend class Json; friend class JsonInt; friend class JsonDouble; virtual Json::Type type() const = 0; virtual bool equals(const JsonValue* other) const = 0; virtual bool less(const JsonValue* other) const = 0; virtual void dump(std::string& out) const = 0; virtual double number_value() const; virtual int int_value() const; virtual bool bool_value() const; virtual const std::string& string_value() const; virtual const Json::array& array_items() const; virtual const Json& operator[](size_t i) const; virtual const Json::object& object_items() const; virtual const Json& operator[](const std::string& key) const; virtual ~JsonValue() {} }; 不難看出,這個類是一個抽象類,這里我就有一個疑問:為什么析構(gòu)函數(shù)沒設(shè)置成純虛的?而是提供了一個空實(shí)現(xiàn)? 因?yàn)槿绻鰳?gòu)函數(shù)也設(shè)置成了純虛函數(shù),繼承它的所有類都強(qiáng)制需要重寫析構(gòu)函數(shù),但是==這里提供了空實(shí)現(xiàn)的話,就可以使用編譯器默認(rèn)提供的析構(gòu)函數(shù)==,會省很多事,同時可以避免一些可能的bug。 snprinf() // 第三個參數(shù)用于指定格式化形式 int snprintf(char* str, size_t maxlen, const char* format, ...) 之前就看到了這個,說說這個函數(shù)的作用吧: snprintf函數(shù)用于將格式化的數(shù)據(jù)輸出到字符數(shù)組中,允許將數(shù)據(jù)格式化為指定格式的字符串。它以字符數(shù)組和格式化的方式工作,是一種基于C語言的函數(shù)。 上面提到了格式化的數(shù)據(jù),什么是格式化的數(shù)據(jù)呢?就是將數(shù)據(jù)以一定格式進(jìn)行處理,比如我們在使用printf輸出小數(shù)的時候,我們能對輸出的小數(shù)的位數(shù)進(jìn)行規(guī)定,就是這個意思。 之前就想到了使用stringstream做類似的操作,但是現(xiàn)在看來這里選擇使用snprintf()是有道理的。 stringstream由于是不定長的,它是動態(tài)管理內(nèi)存,而snprintf()是直接對字符數(shù)組進(jìn)行處理,在傳參的時候就已經(jīng)規(guī)定了緩沖區(qū)的大小,因此相比起stringstream,它的效率更高。 Json解析對性能有較高要求,因此更適合使用snprintf(),附上源碼: static void dump(double value, string &out) { if (std::isfinite(value)) { char buf[32]; snprintf(buf, sizeof buf, "%.17g", value); out += buf; } else { out += "null"; } } Json數(shù)據(jù)中,”"“要進(jìn)行處理 在對字符串的處理中,有如下內(nèi)容: if (ch == '"') { out += "\\\""; } 因?yàn)樵?JSON 格式中,雙引號是用來界定字符串值的起始和結(jié)束的標(biāo)記,如果字符串中本身就含有雙引號,為避免與 JSON 字符串的雙引號沖突,需要進(jìn)行轉(zhuǎn)義處理。 這里可能需要去看看Json的內(nèi)容。 構(gòu)造函數(shù)非公有(public)(疑惑) template class Value : public JsonValue { protected: // Constructors explicit Value(const T &value) : m_value(value) {} explicit Value(T &&value) : m_value(move(value)) {} // Get type tag Json::Type type() const override { return tag; } // Comparisons bool equals(const JsonValue * other) const override { return m_value == static_cast } bool less(const JsonValue * other) const override { return m_value < static_cast } const T m_value; void dump(string &out) const override { json11::dump(m_value, out); } }; 主要的疑惑就是在這個構(gòu)造函數(shù)上,它被聲明為protected的,和private一樣類外無法訪問。 豈不是說,我根本沒法創(chuàng)建Value類型的對象?那么,這么做有何用? 解答:裝飾類,裝飾模式 裝飾類是一種設(shè)計(jì)模式,屬于結(jié)構(gòu)性設(shè)計(jì)設(shè)計(jì)模式之一。 在裝飾模式中,我們可以動態(tài)地給一個對象添加一些額外的職能,而不需繼承自子類。這種模式通過創(chuàng)建一個包裝類來包裹一個原始類的對象。然后按需擴(kuò)展其功能。從而實(shí)現(xiàn)對對象的功能增強(qiáng)而不改變原有的類結(jié)構(gòu)。 在項(xiàng)目的后面,有很多其它的類繼承自該類,如:JsonObject、JsonInt等,這很符合裝飾模式的特點(diǎn)。 裝飾模式通常具有以下要素: 抽象構(gòu)件(Component):定義了對象的接口,可以是一個抽象類或者接口,聲明了對象的基本功能具體構(gòu)建(ConcreteComponent):實(shí)現(xiàn)了抽象構(gòu)建接口,是被裝飾的類裝飾者(Decorator):持有一個抽象構(gòu)件的引用并實(shí)現(xiàn)其接口,負(fù)責(zé)給對象動態(tài)添加新的功能具體裝飾者(ConcreteDecorator):實(shí)現(xiàn)了裝飾者的接口,并對具體構(gòu)件進(jìn)行裝飾。即:擴(kuò)展或改變核心功能 所以在該項(xiàng)目中,有個最原始的類JsonValue,它用來定義最原始的接口,然后再使用Value進(jìn)行裝飾,最后使用JsonObject等子類對功能進(jìn)行拓展。 為什么右值在傳值的時候還需要使用std::move(疑惑) 源碼如下: explicit Value(T&& value) : m_value(move(value)) {} 明明接收的就是一個右值,為什么還使用move呢? 數(shù)據(jù)一致性的實(shí)現(xiàn) struct Statics { const std::shared_ptr const std::shared_ptr const std::shared_ptr const string empty_string; const vector const map Statics() {} }; static const Statics & statics() { static const Statics s {}; return s; } static const Json & static_null() { // This has to be separate, not in Statics, because Json() accesses statics().null. static const Json json_null; return json_null; } 在Json數(shù)據(jù)中,很多地方這些空值需要重復(fù)使用,但是只有全局變量使用的是默認(rèn)值初始化,這個類定義了各種類型的Json數(shù)據(jù)的初始值和空值,這樣可以避免一些隱藏的bug,并且使用靜態(tài)方法和常量,確保了程序的安全性。 當(dāng)我們想要初始化一個Json數(shù)據(jù)的時候,相對應(yīng)地使用這些已經(jīng)創(chuàng)建好的數(shù)據(jù)就行了。 匿名命名空間的作用 匿名命名空間用于限制命名空間內(nèi)的符號的作用域,使得這些符號只能在當(dāng)前編譯單元內(nèi)訪問,并且避免與其他編譯單元或全局作用域內(nèi)的相同名字的符號發(fā)生沖突。 上文中提到的編譯單元就是指的當(dāng)前源代碼文件。 在編譯過程中,每個源代碼文件會被編譯器單獨(dú)編譯成一個編譯單元,然后這些編譯單元最終會被鏈接,成為可執(zhí)行文件或者庫。 namespace{ ... } 在此處,我們將Json解析的一個類放在這個匿名空間中,用于監(jiān)控Json解析的狀態(tài)、進(jìn)度等。 柚子快報邀請碼778899分享:C++Json項(xiàng)目筆記 相關(guān)文章
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場。
轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。