偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

C++程序員Protocol Buffers基礎(chǔ)指南

開(kāi)發(fā) 后端
這篇教程提供了一個(gè)面向 C++ 程序員關(guān)于 protocol buffers 的基礎(chǔ)介紹。使用 Protocol buffers,你需要寫(xiě)一個(gè) .proto 說(shuō)明,用于描述你所希望存儲(chǔ)的數(shù)據(jù)結(jié)構(gòu)。利用 .proto 文件,protocol buffer 編譯器可以創(chuàng)建一個(gè)類(lèi),用于實(shí)現(xiàn)對(duì)高效的二進(jìn)制格式的 protocol buffer 數(shù)據(jù)的自動(dòng)化編碼和解碼。

這篇教程提供了一個(gè)面向 C++ 程序員關(guān)于 protocol buffers 的基礎(chǔ)介紹。通過(guò)創(chuàng)建一個(gè)簡(jiǎn)單的示例應(yīng)用程序,它將向我們展示:

  • 在 .proto 文件中定義消息格式
  • 使用 protocol buffer 編譯器
  • 使用 C++ protocol buffer API 讀寫(xiě)消息

這不是一個(gè)關(guān)于在 C++ 中使用 protocol buffers 的全面指南。要獲取更詳細(xì)的信息,請(qǐng)參考 Protocol Buffer Language GuideEncoding Reference。

為什么使用 Protocol Buffers

我們接下來(lái)要使用的例子是一個(gè)非常簡(jiǎn)單的"地址簿"應(yīng)用程序,它能從文件中讀取聯(lián)系人詳細(xì)信息。地址簿中的每一個(gè)人都有一個(gè)名字、ID、郵件地址和聯(lián)系電話。

如何序列化和獲取結(jié)構(gòu)化的數(shù)據(jù)?這里有幾種解決方案:

  • 以二進(jìn)制形式發(fā)送/接收原生的內(nèi)存數(shù)據(jù)結(jié)構(gòu)。通常,這是一種脆弱的方法,因?yàn)榻邮?讀取代碼必須基于完全相同的內(nèi)存布局、大小端等環(huán)境進(jìn)行編譯。同時(shí),當(dāng)文件增加時(shí),原始格式數(shù)據(jù)會(huì)隨著與該格式相關(guān)的軟件而迅速擴(kuò)散,這將導(dǎo)致很難擴(kuò)展文件格式。
  • 你可以創(chuàng)造一種 ad-hoc 方法,將數(shù)據(jù)項(xiàng)編碼為一個(gè)字符串——比如將 4 個(gè)整數(shù)編碼為 12:3:-23:67。雖然它需要編寫(xiě)一次性的編碼和解碼代碼且解碼需要耗費(fèi)一點(diǎn)運(yùn)行時(shí)成本,但這是一種簡(jiǎn)單靈活的方法。這最適合編碼非常簡(jiǎn)單的數(shù)據(jù)。
  • 序列化數(shù)據(jù)為 XML。這種方法是非常吸引人的,因?yàn)?XML 是一種適合人閱讀的格式,并且有為許多語(yǔ)言開(kāi)發(fā)的庫(kù)。如果你想與其他程序和項(xiàng)目共享數(shù)據(jù),這可能是一種不錯(cuò)的選擇。然而,眾所周知,XML 是空間密集型的,且在編碼和解碼時(shí),它對(duì)程序會(huì)造成巨大的性能損失。同時(shí),使用 XML DOM 樹(shù)被認(rèn)為比操作一個(gè)類(lèi)的簡(jiǎn)單字段更加復(fù)雜。

Protocol buffers 是針對(duì)這個(gè)問(wèn)題的一種靈活、高效、自動(dòng)化的解決方案。使用 Protocol buffers,你需要寫(xiě)一個(gè) .proto 說(shuō)明,用于描述你所希望存儲(chǔ)的數(shù)據(jù)結(jié)構(gòu)。利用 .proto 文件,protocol buffer 編譯器可以創(chuàng)建一個(gè)類(lèi),用于實(shí)現(xiàn)對(duì)高效的二進(jìn)制格式的 protocol buffer 數(shù)據(jù)的自動(dòng)化編碼和解碼。產(chǎn)生的類(lèi)提供了構(gòu)造 protocol buffer 的字段的 getters 和 setters,并且作為一個(gè)單元來(lái)處理讀寫(xiě) protocol buffer 的細(xì)節(jié)。重要的是,protocol buffer 格式支持格式的擴(kuò)展,代碼仍然可以讀取以舊格式編碼的數(shù)據(jù)。

在哪可以找到示例代碼

示例代碼被包含于源代碼包,位于“examples”文件夾??稍?a >這里下載代碼。

定義你的協(xié)議格式

為了創(chuàng)建自己的地址簿應(yīng)用程序,你需要從 .proto 開(kāi)始。.proto 文件中的定義很簡(jiǎn)單:為你所需要序列化的每個(gè)數(shù)據(jù)結(jié)構(gòu)添加一個(gè)消息(message),然后為消息中的每一個(gè)字段指定一個(gè)名字和類(lèi)型。這里是定義你消息的 .proto 文件 addressbook.proto。

  1. package tutorial; 
  2. message Person { 
  3.   required string name = 1; 
  4.   required int32 id = 2; 
  5.   optional string email = 3; 
  6.   enum PhoneType { 
  7.     MOBILE = 0; 
  8.     HOME = 1; 
  9.     WORK = 2; 
  10.   } 
  11.   message PhoneNumber { 
  12.     required string number = 1; 
  13.     optional PhoneType type = 2 [default = HOME]; 
  14.   } 
  15.   repeated PhoneNumber phone = 4; 
  16. message AddressBook { 
  17.   repeated Person person = 1; 

如你所見(jiàn),其語(yǔ)法類(lèi)似于 C++ 或 Java。我們開(kāi)始看看文件的每一部分內(nèi)容做了什么。

.proto 文件以一個(gè) package 聲明開(kāi)始,這可以避免不同項(xiàng)目的命名沖突。在 C++,你生成的類(lèi)會(huì)被置于與 package 名字一樣的命名空間。

下一步,你需要定義消息(message)。消息只是一個(gè)包含一系列類(lèi)型字段的集合。大多標(biāo)準(zhǔn)的簡(jiǎn)單數(shù)據(jù)類(lèi)型是可以作為字段類(lèi)型的,包括 bool、int32、float、double 和 string。你也可以通過(guò)使用其他消息類(lèi)型作為字段類(lèi)型,將更多的數(shù)據(jù)結(jié)構(gòu)添加到你的消息中——在以上的示例,Person 消息包含了 PhoneNumber 消息,同時(shí) AddressBook 消息包含 Person 消息。你甚至可以定義嵌套在其他消息內(nèi)的消息類(lèi)型——如你所見(jiàn),PhoneNumber 類(lèi)型定義于 Person 內(nèi)部。如果你想要其中某一個(gè)字段的值是預(yù)定義值列表中的某個(gè)值,你也可以定義 enum 類(lèi)型——這兒你可以指定一個(gè)電話號(hào)碼是 MOBILE、HOME 或 WORK 中的某一個(gè)。

每一個(gè)元素上的 = 1、= 2 標(biāo)記確定了用于二進(jìn)制編碼的唯一“標(biāo)簽”(tag)。標(biāo)簽數(shù)字 1-15 的編碼比更大的數(shù)字少需要一個(gè)字節(jié),因此作為一種優(yōu)化,你可以將這些標(biāo)簽用于經(jīng)常使用的元素或 repeated 元素,剩下 16 以及更高的標(biāo)簽用于非經(jīng)常使用的元素或 optional 元素。每一個(gè) repeated 字段的元素需要重新編碼標(biāo)簽數(shù)字,因此 repeated 字段適合于使用這種優(yōu)化手段。

每一個(gè)字段必須使用下面的修飾符加以標(biāo)注:

  • required:必須提供該字段的值,否則消息會(huì)被認(rèn)為是 “未初始化的”(uninitialized)。如果 libprotobuf 以調(diào)試模式編譯,序列化未初始化的消息將引起一個(gè)斷言失敗。以優(yōu)化形式構(gòu)建,將會(huì)跳過(guò)檢查,并且無(wú)論如何都會(huì)寫(xiě)入該消息。然而,解析未初始化的消息總是會(huì)失敗(通過(guò) parse 方法返回 false)。除此之外,一個(gè) required 字段的表現(xiàn)與 optional 字段完全一樣。
  • optional:字段可能會(huì)被設(shè)置,也可能不會(huì)。如果一個(gè) optional 字段沒(méi)被設(shè)置,它將使用默認(rèn)值。對(duì)于簡(jiǎn)單類(lèi)型,你可以指定你自己的默認(rèn)值,正如例子中我們對(duì)電話號(hào)碼的 type 一樣,否則使用系統(tǒng)默認(rèn)值:數(shù)字類(lèi)型為 0、字符串為空字符串、布爾值為 false。對(duì)于嵌套消息,默認(rèn)值總為消息的“默認(rèn)實(shí)例”或“原型”,它的所有字段都沒(méi)被設(shè)置。調(diào)用 accessor 來(lái)獲取一個(gè)沒(méi)有顯式設(shè)置的 optional(或 required) 字段的值總是返回字段的默認(rèn)值。
  • repeated:字段可以重復(fù)任意次數(shù)(包括 0 次)。repeated 值的順序會(huì)被保存于 protocol buffer??梢詫?repeated 字段想象為動(dòng)態(tài)大小的數(shù)組。

你可以查找關(guān)于編寫(xiě) .proto 文件的完整指導(dǎo)——包括所有可能的字段類(lèi)型——在 Protocol Buffer Language Guide 里面。不要在這里面查找與類(lèi)繼承相似的特性,因?yàn)?protocol buffers 不會(huì)做這些。

required 是***性的

在把一個(gè)字段標(biāo)識(shí)為 required 的時(shí)候,你應(yīng)該特別小心。如果在某些情況下你不想寫(xiě)入或者發(fā)送一個(gè) required 的字段,那么將該字段更改為 optional 可能會(huì)遇到問(wèn)題——舊版本的讀者(LCTT 譯注:即讀取、解析舊版本 Protocol Buffer 消息的一方)會(huì)認(rèn)為不含該字段的消息是不完整的,從而有可能會(huì)拒絕解析。在這種情況下,你應(yīng)該考慮編寫(xiě)特別針對(duì)于應(yīng)用程序的、自定義的消息校驗(yàn)函數(shù)。Google 的一些工程師得出了一個(gè)結(jié)論:使用 required 弊多于利;他們更愿意使用 optional 和 repeated 而不是 required。當(dāng)然,這個(gè)觀點(diǎn)并不具有普遍性。

編譯你的 Protocol Buffers

既然你有了一個(gè) .proto,那你需要做的下一件事就是生成一個(gè)將用于讀寫(xiě) AddressBook 消息的類(lèi)(從而包括 Person 和 PhoneNumber)。為了做到這樣,你需要在你的 .proto 上運(yùn)行 protocol buffer 編譯器 protoc:

  1. 如果你沒(méi)有安裝編譯器,請(qǐng)下載這個(gè)包,并按照 README 中的指令進(jìn)行安裝。
  2. 現(xiàn)在運(yùn)行編譯器,指定源目錄(你的應(yīng)用程序源代碼位于哪里——如果你沒(méi)有提供任何值,將使用當(dāng)前目錄)、目標(biāo)目錄(你想要生成的代碼放在哪里;常與 $SRC_DIR 相同),以及你的 .proto 路徑。在此示例中:
  1. protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/addressbook.proto 

因?yàn)槟阆胍?C++ 的類(lèi),所以你使用了 --cpp_out 選項(xiàng)——也為其他支持的語(yǔ)言提供了類(lèi)似選項(xiàng)。

在你指定的目標(biāo)文件夾,將生成以下的文件:

  • addressbook.pb.h,聲明你生成類(lèi)的頭文件。
  • addressbook.pb.cc,包含你的類(lèi)的實(shí)現(xiàn)。

Protocol Buffer API

讓我們看看生成的一些代碼,了解一下編譯器為你創(chuàng)建了什么類(lèi)和函數(shù)。如果你查看 addressbook.pb.h,你可以看到有一個(gè)在 addressbook.proto 中指定所有消息的類(lèi)。關(guān)注 Person 類(lèi),可以看到編譯器為每個(gè)字段生成了讀寫(xiě)函數(shù)(accessors)。例如,對(duì)于 name、id、email 和 phone 字段,有下面這些方法:(LCTT 譯注:此處原文所指文件名有誤,徑該之。)

  1. // name 
  2. inline bool has_name() const; 
  3. inline void clear_name(); 
  4. inline const ::std::string& name() const; 
  5. inline void set_name(const ::std::string& value); 
  6. inline void set_name(const char* value); 
  7. inline ::std::string* mutable_name(); 
  8. // id 
  9. inline bool has_id() const; 
  10. inline void clear_id(); 
  11. inline int32_t id() const; 
  12. inline void set_id(int32_t value); 
  13. // email 
  14. inline bool has_email() const; 
  15. inline void clear_email(); 
  16. inline const ::std::string& email() const; 
  17. inline void set_email(const ::std::string& value); 
  18. inline void set_email(const char* value); 
  19. inline ::std::string* mutable_email(); 
  20. // phone 
  21. inline int phone_size() const; 
  22. inline void clear_phone(); 
  23. inline const ::google::protobuf::RepeatedPtrField< ::tutorial::Person_PhoneNumber >& phone() const; 
  24. inline ::google::protobuf::RepeatedPtrField< ::tutorial::Person_PhoneNumber >* mutable_phone(); 
  25. inline const ::tutorial::Person_PhoneNumber& phone(int index) const; 
  26. inline ::tutorial::Person_PhoneNumber* mutable_phone(int index); 
  27. inline ::tutorial::Person_PhoneNumber* add_phone(); 

正如你所見(jiàn)到,getters 的名字與字段的小寫(xiě)名字完全一樣,并且 setter 方法以 set_ 開(kāi)頭。同時(shí)每個(gè)單一(singular)(required 或 optional)字段都有 has_ 方法,該方法在字段被設(shè)置了值的情況下返回 true。***,所有字段都有一個(gè) clear_ 方法,用以清除字段到空(empty)狀態(tài)。

數(shù)字型的 id 字段僅有上述的基本讀寫(xiě)函數(shù)(accessors)集合,而 name 和 email 字段有兩個(gè)額外的方法,因?yàn)樗鼈兪亲址?mdash;—一個(gè)是可以獲得字符串直接指針的mutable_ 的 getter ,另一個(gè)為額外的 setter。注意,盡管 email 還沒(méi)被設(shè)置(set),你也可以調(diào)用 mutable_email;因?yàn)?email 會(huì)被自動(dòng)地初始化為空字符串。在本例中,如果你有一個(gè)單一的(required 或 optional)消息字段,它會(huì)有一個(gè) mutable_ 方法,而沒(méi)有 set_ 方法。

repeated 字段也有一些特殊的方法——如果你看看 repeated 的 phone 字段的方法,你可以看到:

  • 檢查 repeated 字段的 _size(也就是說(shuō),與 Person 相關(guān)的電話號(hào)碼的個(gè)數(shù))
  • 使用下標(biāo)取得特定的電話號(hào)碼
  • 更新特定下標(biāo)的電話號(hào)碼
  • 添加新的電話號(hào)碼到消息中,之后你便可以編輯。(repeated 標(biāo)量類(lèi)型有一個(gè) add_ 方法,用于傳入新的值)

為了獲取 protocol 編譯器為所有字段定義生成的方法的信息,可以查看 C++ generated code reference。

枚舉和嵌套類(lèi)

與 .proto 的枚舉相對(duì)應(yīng),生成的代碼包含了一個(gè) PhoneType 枚舉。你可以通過(guò) Person::PhoneType 引用這個(gè)類(lèi)型,通過(guò) Person::MOBILE、Person::HOME 和 Person::WORK 引用它的值。(實(shí)現(xiàn)細(xì)節(jié)有點(diǎn)復(fù)雜,但是你無(wú)須了解它們而可以直接使用)

編譯器也生成了一個(gè) Person::PhoneNumber 的嵌套類(lèi)。如果你查看代碼,你可以發(fā)現(xiàn)真正的類(lèi)型為 Person_PhoneNumber,但它通過(guò)在 Person 內(nèi)部使用 typedef 定義,使你可以把 Person_PhoneNumber 當(dāng)成嵌套類(lèi)。唯一產(chǎn)生影響的一個(gè)例子是,如果你想要在其他文件前置聲明該類(lèi)——在 C++ 中你不能前置聲明嵌套類(lèi),但是你可以前置聲明 Person_PhoneNumber。

標(biāo)準(zhǔn)的消息方法

所有的消息方法都包含了許多別的方法,用于檢查和操作整個(gè)消息,包括:

  • bool IsInitialized() const; :檢查是否所有 required 字段已經(jīng)被設(shè)置。
  • string DebugString() const; :返回人類(lèi)可讀的消息表示,對(duì)調(diào)試特別有用。
  • void CopyFrom(const Person& from);:使用給定的值重寫(xiě)消息。
  • void Clear();:清除所有元素為空的狀態(tài)。

上面這些方法以及下一節(jié)要講的 I/O 方法實(shí)現(xiàn)了被所有 C++ protocol buffer 類(lèi)共享的消息(Message)接口。為了獲取更多信息,請(qǐng)查看 complete API documentation for Message。

解析和序列化

***,所有 protocol buffer 類(lèi)都有讀寫(xiě)你選定類(lèi)型消息的方法,這些方法使用了特定的 protocol buffer 二進(jìn)制格式。這些方法包括:

  • bool SerializeToString(string* output) const;:序列化消息并將消息字節(jié)數(shù)據(jù)存儲(chǔ)在給定的字符串中。注意,字節(jié)數(shù)據(jù)是二進(jìn)制格式的,而不是文本格式;我們只使用 string 類(lèi)作為合適的容器。
  • bool ParseFromString(const string& data);:從給定的字符創(chuàng)解析消息。
  • bool SerializeToOstream(ostream* output) const;:將消息寫(xiě)到給定的 C++ ostream。
  • bool ParseFromIstream(istream* input);:從給定的 C++ istream 解析消息。

這些只是兩個(gè)用于解析和序列化的選擇。再次說(shuō)明,可以查看 Message API reference 完整的列表。

Protocol Buffers 和面向?qū)ο笤O(shè)計(jì)

Protocol buffer 類(lèi)通常只是純粹的數(shù)據(jù)存儲(chǔ)器(像 C++ 中的結(jié)構(gòu)體);它們?cè)趯?duì)象模型中并不是一等公民。如果你想向生成的 protocol buffer 類(lèi)中添加更豐富的行為,***的方法就是在應(yīng)用程序中對(duì)它進(jìn)行封裝。如果你無(wú)權(quán)控制 .proto 文件的設(shè)計(jì)的話,封裝 protocol buffers 也是一個(gè)好主意(例如,你從另一個(gè)項(xiàng)目中重用一個(gè) .proto 文件)。在那種情況下,你可以用封裝類(lèi)來(lái)設(shè)計(jì)接口,以更好地適應(yīng)你的應(yīng)用程序的特定環(huán)境:隱藏一些數(shù)據(jù)和方法,暴露一些便于使用的函數(shù),等等。但是你絕對(duì)不要通過(guò)繼承生成的類(lèi)來(lái)添加行為。這樣做的話,會(huì)破壞其內(nèi)部機(jī)制,并且不是一個(gè)好的面向?qū)ο蟮膶?shí)踐。

寫(xiě)消息

現(xiàn)在我們嘗試使用 protocol buffer 類(lèi)。你的地址簿程序想要做的***件事是將個(gè)人詳細(xì)信息寫(xiě)入到地址簿文件。為了做到這一點(diǎn),你需要?jiǎng)?chuàng)建、填充 protocol buffer 類(lèi)實(shí)例,并且將它們寫(xiě)入到一個(gè)輸出流(output stream)。

這里的程序可以從文件讀取 AddressBook,根據(jù)用戶輸入,將新 Person 添加到 AddressBook,并且再次將新的 AddressBook 寫(xiě)回文件。這部分直接調(diào)用或引用 protocol buffer 類(lèi)的代碼會(huì)以“// pb”標(biāo)出。

  1. #include <iostream> 
  2. #include <fstream> 
  3. #include <string> 
  4. #include "addressbook.pb.h" // pb 
  5. using namespace std; 
  6. // This function fills in a Person message based on user input. 
  7. void PromptForAddress(tutorial::Person* person) { 
  8.   cout << "Enter person ID number: "
  9.   int id; 
  10.   cin >> id; 
  11.   person->set_id(id);   // pb 
  12.   cin.ignore(256, '\n'); 
  13.   cout << "Enter name: "
  14.   getline(cin, *person->mutable_name());    // pb 
  15.   cout << "Enter email address (blank for none): "
  16.   string email; 
  17.   getline(cin, email); 
  18.   if (!email.empty()) { // pb 
  19.     person->set_email(email);   // pb 
  20.   } 
  21.   while (true) { 
  22.     cout << "Enter a phone number (or leave blank to finish): "
  23.     string number; 
  24.     getline(cin, number); 
  25.     if (number.empty()) { 
  26.       break; 
  27.     } 
  28.     tutorial::Person::PhoneNumber* phone_number = person->add_phone();  //pb 
  29.     phone_number->set_number(number);   // pb 
  30.     cout << "Is this a mobile, home, or work phone? "
  31.     string type; 
  32.     getline(cin, type); 
  33.     if (type == "mobile") { 
  34.       phone_number->set_type(tutorial::Person::MOBILE); // pb 
  35.     } else if (type == "home") { 
  36.       phone_number->set_type(tutorial::Person::HOME);   // pb 
  37.     } else if (type == "work") { 
  38.       phone_number->set_type(tutorial::Person::WORK);   // pb 
  39.     } else { 
  40.       cout << "Unknown phone type.  Using default." << endl; 
  41.     } 
  42.   } 
  43. // Main function:  Reads the entire address book from a file, 
  44. //   adds one person based on user input, then writes it back out to the same 
  45. //   file. 
  46. int main(int argc, char* argv[]) { 
  47.   // Verify that the version of the library that we linked against is 
  48.   // compatible with the version of the headers we compiled against. 
  49.   GOOGLE_PROTOBUF_VERIFY_VERSION;   // pb 
  50.   if (argc != 2) { 
  51.     cerr << "Usage:  " << argv[0] << " ADDRESS_BOOK_FILE" << endl; 
  52.     return -1; 
  53.   } 
  54.   tutorial::AddressBook address_book;   // pb 
  55.   { 
  56.     // Read the existing address book. 
  57.     fstream input(argv[1], ios::in | ios::binary); 
  58.     if (!input) { 
  59.       cout << argv[1] << ": File not found.  Creating a new file." << endl; 
  60.     } else if (!address_book.ParseFromIstream(&input)) {    // pb 
  61.       cerr << "Failed to parse address book." << endl; 
  62.       return -1; 
  63.     } 
  64.   } 
  65.   // Add an address. 
  66.   PromptForAddress(address_book.add_person());  // pb 
  67.   { 
  68.     // Write the new address book back to disk. 
  69.     fstream output(argv[1], ios::out | ios::trunc | ios::binary); 
  70.     if (!address_book.SerializeToOstream(&output)) {    // pb 
  71.       cerr << "Failed to write address book." << endl; 
  72.       return -1; 
  73.     } 
  74.   } 
  75.   // Optional:  Delete all global objects allocated by libprotobuf. 
  76.   google::protobuf::ShutdownProtobufLibrary();  // pb 
  77.   return 0; 

注意 GOOGLE_PROTOBUF_VERIFY_VERSION 宏。它是一種好的實(shí)踐——雖然不是嚴(yán)格必須的——在使用 C++ Protocol Buffer 庫(kù)之前執(zhí)行該宏。它可以保證避免不小心鏈接到一個(gè)與編譯的頭文件版本不兼容的庫(kù)版本。如果被檢查出來(lái)版本不匹配,程序?qū)?huì)終止。注意,每個(gè) .pb.cc 文件在初始化時(shí)會(huì)自動(dòng)調(diào)用這個(gè)宏。

同時(shí)注意在程序***調(diào)用 ShutdownProtobufLibrary()。它用于釋放 Protocol Buffer 庫(kù)申請(qǐng)的所有全局對(duì)象。對(duì)大部分程序,這不是必須的,因?yàn)殡m然程序只是簡(jiǎn)單退出,但是 OS 會(huì)處理釋放程序的所有內(nèi)存。然而,如果你使用了內(nèi)存泄漏檢測(cè)工具,工具要求全部對(duì)象都要釋放,或者你正在寫(xiě)一個(gè) Protocol Buffer 庫(kù),該庫(kù)可能會(huì)被一個(gè)進(jìn)程多次加載和卸載,那么你可能需要強(qiáng)制 Protocol Buffer 清除所有東西。

讀取消息

當(dāng)然,如果你無(wú)法從它獲取任何信息,那么這個(gè)地址簿沒(méi)多大用處!這個(gè)示例讀取上面例子創(chuàng)建的文件,并打印文件里的所有內(nèi)容。

  1. #include <iostream> 
  2. #include <fstream> 
  3. #include <string> 
  4. #include "addressbook.pb.h" // pb 
  5. using namespace std; 
  6. // Iterates though all people in the AddressBook and prints info about them. 
  7. void ListPeople(const tutorial::AddressBook& address_book) {    // pb 
  8.   for (int i = 0; i < address_book.person_size(); i++) {        // pb 
  9.     const tutorial::Person& person = address_book.person(i);    // pb 
  10.     cout << "Person ID: " << person.id() << endl;   // pb 
  11.     cout << "  Name: " << person.name() << endl;    // pb 
  12.     if (person.has_email()) {   // pb 
  13.       cout << "  E-mail address: " << person.email() << endl;   // pb 
  14.     } 
  15.     for (int j = 0; j < person.phone_size(); j++) { // pb 
  16.       const tutorial::Person::PhoneNumber& phone_number = person.phone(j);  // pb 
  17.       switch (phone_number.type()) {    // pb 
  18.         case tutorial::Person::MOBILE:  // pb 
  19.           cout << "  Mobile phone #: "
  20.           break; 
  21.         case tutorial::Person::HOME:    // pb 
  22.           cout << "  Home phone #: "
  23.           break; 
  24.         case tutorial::Person::WORK:    // pb 
  25.           cout << "  Work phone #: "
  26.           break; 
  27.       } 
  28.       cout << phone_number.number() << endl;    // ob 
  29.     } 
  30.   } 
  31. // Main function:  Reads the entire address book from a file and prints all 
  32. //   the information inside. 
  33. int main(int argc, char* argv[]) { 
  34.   // Verify that the version of the library that we linked against is 
  35.   // compatible with the version of the headers we compiled against. 
  36.   GOOGLE_PROTOBUF_VERIFY_VERSION;   // pb 
  37.   if (argc != 2) { 
  38.     cerr << "Usage:  " << argv[0] << " ADDRESS_BOOK_FILE" << endl; 
  39.     return -1; 
  40.   } 
  41.   tutorial::AddressBook address_book;   // pb 
  42.   { 
  43.     // Read the existing address book. 
  44.     fstream input(argv[1], ios::in | ios::binary); 
  45.     if (!address_book.ParseFromIstream(&input)) {   // pb 
  46.       cerr << "Failed to parse address book." << endl; 
  47.       return -1; 
  48.     } 
  49.   } 
  50.   ListPeople(address_book); 
  51.   // Optional:  Delete all global objects allocated by libprotobuf. 
  52.   google::protobuf::ShutdownProtobufLibrary();  // pb 
  53.   return 0; 

擴(kuò)展 Protocol Buffer

或早或晚在你發(fā)布了使用 protocol buffer 的代碼之后,毫無(wú)疑問(wèn),你會(huì)想要 "改善" protocol buffer 的定義。如果你想要新的 buffers 向后兼容,并且老的 buffers 向前兼容——幾乎可以肯定你很渴望這個(gè)——這里有一些規(guī)則,你需要遵守。在新的 protocol buffer 版本:

  • 你絕不可以修改任何已存在字段的標(biāo)簽數(shù)字
  • 你絕不可以添加或刪除任何 required 字段
  • 你可以刪除 optional 或 repeated 字段
  • 你可以添加新的 optional 或 repeated 字段,但是你必須使用新的標(biāo)簽數(shù)字(也就是說(shuō),標(biāo)簽數(shù)字在 protocol buffer 中從未使用過(guò),甚至不能是已刪除字段的標(biāo)簽數(shù)字)。

(對(duì)于上面規(guī)則有一些例外情況,但它們很少用到。)

如果你能遵守這些規(guī)則,舊代碼則可以歡快地讀取新的消息,并且簡(jiǎn)單地忽略所有新的字段。對(duì)于舊代碼來(lái)說(shuō),被刪除的 optional 字段將會(huì)簡(jiǎn)單地賦予默認(rèn)值,被刪除的 repeated 字段會(huì)為空。新代碼顯然可以讀取舊消息。然而,請(qǐng)記住新的 optional 字段不會(huì)呈現(xiàn)在舊消息中,因此你需要顯式地使用 has_ 檢查它們是否被設(shè)置或者在 .proto 文件在標(biāo)簽數(shù)字后使用 [default = value] 提供一個(gè)合理的默認(rèn)值。如果一個(gè) optional 元素沒(méi)有指定默認(rèn)值,它將會(huì)使用類(lèi)型特定的默認(rèn)值:對(duì)于字符串,默認(rèn)值為空字符串;對(duì)于布爾值,默認(rèn)值為 false;對(duì)于數(shù)字類(lèi)型,默認(rèn)類(lèi)型為 0。注意,如果你添加一個(gè)新的 repeated 字段,新代碼將無(wú)法辨別它被留空(被新代碼)或者從沒(méi)被設(shè)置(被舊代碼),因?yàn)? repeated 字段沒(méi)有 has_ 標(biāo)志。

優(yōu)化技巧

C++ Protocol Buffer 庫(kù)已極度優(yōu)化過(guò)了。但是,恰當(dāng)?shù)挠梅軌蚋嗟靥岣咝阅?。這里是一些技巧,可以幫你從庫(kù)中擠壓出***一點(diǎn)速度:

  • 盡可能復(fù)用消息對(duì)象。即使它們被清除掉,消息也會(huì)盡量保存所有被分配來(lái)重用的內(nèi)存。因此,如果我們正在處理許多相同類(lèi)型或一系列相似結(jié)構(gòu)的消息,一個(gè)好的辦法是重用相同的消息對(duì)象,從而減少內(nèi)存分配的負(fù)擔(dān)。但是,隨著時(shí)間的流逝,對(duì)象可能會(huì)膨脹變大,尤其是當(dāng)你的消息尺寸(LCTT 譯注:各消息內(nèi)容不同,有些消息內(nèi)容多一些,有些消息內(nèi)容少一些)不同的時(shí)候,或者你偶爾創(chuàng)建了一個(gè)比平常大很多的消息的時(shí)候。你應(yīng)該自己通過(guò)調(diào)用 SpaceUsed 方法監(jiān)測(cè)消息對(duì)象的大小,并在它太大的時(shí)候刪除它。
  • 對(duì)于在多線程中分配大量小對(duì)象的情況,你的操作系統(tǒng)內(nèi)存分配器可能優(yōu)化得不夠好。你可以嘗試使用 google 的 tcmalloc。

高級(jí)用法

Protocol Buffers 絕不僅用于簡(jiǎn)單的數(shù)據(jù)存取以及序列化。請(qǐng)閱讀 C++ API reference 來(lái)看看你還能用它來(lái)做什么。

protocol 消息類(lèi)所提供的一個(gè)關(guān)鍵特性就是反射(reflection)。你不需要針對(duì)一個(gè)特殊的消息類(lèi)型編寫(xiě)代碼,就可以遍歷一個(gè)消息的字段并操作它們的值。一個(gè)使用反射的有用方法是 protocol 消息與其他編碼互相轉(zhuǎn)換,比如 XML 或 JSON。反射的一個(gè)更高級(jí)的用法可能就是可以找出兩個(gè)相同類(lèi)型的消息之間的區(qū)別,或者開(kāi)發(fā)某種“協(xié)議消息的正則表達(dá)式”,利用正則表達(dá)式,你可以對(duì)某種消息內(nèi)容進(jìn)行匹配。只要你發(fā)揮你的想像力,就有可能將 Protocol Buffers 應(yīng)用到一個(gè)更廣泛的、你可能一開(kāi)始就期望解決的問(wèn)題范圍上。

責(zé)任編輯:龐桂玉 來(lái)源: segmentfault
相關(guān)推薦

2012-11-08 09:49:30

C++Java程序員

2021-02-26 10:41:59

C++程序員代碼

2010-01-12 10:40:22

C++程序員

2016-03-25 11:57:23

Java程序員C++

2010-01-12 14:30:41

C++程序

2018-04-23 11:00:06

程序員養(yǎng)生健康

2010-01-14 18:07:30

C++語(yǔ)言

2023-07-17 10:28:00

C/C++編程接口

2011-03-30 09:26:20

c++程序員

2015-07-28 17:58:22

程序員指南

2013-07-04 13:50:14

2009-06-22 09:06:57

程序員技術(shù)升級(jí)

2011-04-11 17:41:35

C++程序員

2011-03-29 09:01:27

C++程序員

2010-01-14 13:24:49

CC++語(yǔ)言

2011-05-24 17:20:57

程序員

2023-11-27 19:39:46

Goprotobuf

2010-04-01 09:17:02

C++程序員

2022-05-30 11:46:29

GNU C 編譯器的

2012-02-01 09:30:54

HTML 5
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)