protobuf 中的巢狀訊息的使用 主要對set_allocated_和mutable_的使用

軍說網事發表於2016-04-30

protobuf的簡單的使用,不過還留下了一個問題,那就是之前主要介紹的都是對簡單資料的賦值,簡單資料直接採用set_xx()即可,但是如果不是簡單變數而是自定義的複合型別變數,就沒有簡單的set函式呼叫了,下面看一個簡單的例子。

最近使用protobuf,用set_allocated_pos(),編譯沒問題,但發現賦值不對。


在網路遊戲中,遊戲玩家之間的同步是一個最基本的功能,而同步是通過對座標的廣播進行的,因此我們假設一個簡單的模型,當一個玩家的位置發生變化時,將玩家的新位置發給地圖內所有玩家,根據這個情況寫出以下proto檔案。


1.     message PlayerPos  

2.     {     

3.         required  uint32  playerID = 1;   

4.         required  float   posX = 2 ;      

5.         required  float   posY = 3 ;  

6.     };  

7.       

8.     file  vector.protomessage  vector3D  

9.     {  

10.       required float x = 1;  

11.       required float y = 2;  

12.       required float z = 3;  

13.   };  

這樣就有一個問題,現在的遊戲都是3D遊戲,因此需要xyz來表示位置,還需要另一組xyz來表示朝向,如果用簡單變數的話就會顯的很亂,而且無論是位置還是朝向其實都是一組xyz,因此可以將xyz抽出來成為一個複合資料型別,單獨放在一個檔案中。這樣就構成以下檔案。


1.     file  Player.protoimport "vector.proto";  

2.     message PlayerPos   

3.     {  

4.         required uint32 playerID = 1;  

5.         required vector3D  pos = 2;  

6.     };  

編譯的時候先編譯vector檔案,採用import時需要注意路徑,本例中兩檔案在同一目錄下。


1.     protoc --cpp_out=.  vector.proto  Player.proto  

proto對應的檔案已經生成了,但是該怎麼賦值呢,查API查了半天有點不知所以,乾脆來看生成的類檔案的原始碼吧


1.     // required uint32 playerID = 1;    

2.     inline bool has_playerid() const;    

3.     inline void clear_playerid();    

4.     static const int kPlayerIDFieldNumber = 1;   

5.     inline ::google::protobuf::uint32 playerid() const;    

6.     inline void set_playerid(::google::protobuf::uint32 value);    

7.     // required .vector3D pos = 2;    

8.     inline bool has_pos() const;    

9.     inline void clear_pos();    

10.   static const int kPosFieldNumber = 2;    

11.   inline const ::vector3D& pos() const;    

12.   inline ::vector3D* mutable_pos();    

13.   inline ::vector3D* release_pos();    

14.   inline void set_allocated_pos(::vector3D* pos);  

上面列出了生成的部分原始碼,主要是PlayerPos的操作變數的函式,第一個playID很簡單,可以看到直接使用set_playerid ( ) 即可,但是對於巢狀的pos 發現沒有對應的set_pos方法,不過發現了一個set_allocated_pos()函式,這個函式也是set開頭的,看看這個函式是幹嘛的。


1.     inline void PlayerPos::set_allocated_pos(::vector3D* pos)   

2.     {    

3.         delete pos_;    

4.         pos_ = pos;    

5.         if (pos)   

6.         {      

7.             set_has_pos();    

8.         }   

9.         else {     

10.            clear_has_pos();    

11.       }  

12.   }  

看上去可以賦值,直接呼叫set_allocated_pos() 進行賦值看一看


1.     PlayerPos player;  

2.     vector3D  tmp;  

3.     tmp.x = 1;  

4.     tmp.y = 2;  

5.     tmp.z = 3;  

6.     player.set_allocated_pos(&tmp)  

編譯沒問題,但是執行時出現錯誤,而且是很奇怪的錯誤,仔細了檢視一下PlayerPos的原始碼,發現一個問題


1.     ::vector3D* pos_;  ::google::protobuf::uint32 playerid_;  

上面是PlayerPos中變數的儲存形式,發現pos是作為一個指標儲存的,如果按照之前的賦值 tmp 是一個區域性變數,函式返回時區域性變數自動銷燬,而pos_儲存的仍然是已被銷燬的tmp的位置,因此會出錯,如果採用new的話就可以解決這個問題,即賦值方法如下:


1.     PlayerPos player;vector3D  *tmp = new Vector3D;  

2.     tmp->x = 1;  

3.     tmp->y = 2;  

4.     tmp->z = 3;  

5.     player.set_allocated_pos(tmp)  

這樣即可,編譯執行都沒有問題。 
如此之外,還有一種賦值方法,就是呼叫mutable_pos()


1.     inline ::vector3D* PlayerPos::mutable_pos()   

2.     {    

3.         set_has_pos();    

4.         if (pos_ == NULL)   

5.             pos_ = new ::vector3D;    

6.         return pos_;  

7.     }  

mutable_pos () 中自己new出了一個vector3D 物件,而vector3D中又實現了賦值的過載,因此可以這樣解決:


1.     PlayerPos player;  

2.     vector3D  *tmp = player.mutable_pos();  

3.     tmp->x = 1;  

4.     tmp->y = 2;  

5.     tmp->z = 3;  


總結:protobuf 中的巢狀訊息的使用主要對set_allocated_和mutable_的使用

1 使用set_allocated_,賦值的物件需要new出來,不能用區域性的,這裡儲存的是物件的指標。

2 使用mutable_,賦值時候,可以使用區域性變數,因為在呼叫的時,內部做了new操作。





相關文章