文章目录
- [1. Any 类型](#1. Any 类型)
- [2. 升级通讯录至 2.2 版本](#2. 升级通讯录至 2.2 版本)
1. Any 类型
字段还可以声明为 Any 类型,可以理解为泛型类型。使用时可以在 Any 中存储任意消息类型。Any 类型的字段也用 repeated 来修饰。
Any 类型是 google 已经帮我们定义好的类型,在安装 ProtoBuf 时,其中的 include 目录下查找所有 google 已经定义好的 .proto 文件。
该文件所在的目录如下:
bash
# 进入该目录
cd /usr/local/protobuf/include/google/protobuf/
# 查看文件
cat any.proto
文件内容如下:

接下来,我们继续对通讯录进行升级,我们可以使用 any 类型的字段来存储联系人的地址信息。
2. 升级通讯录至 2.2 版本
2.1 更新 contacts.proto 文件
代码如下所示:
cpp
syntax = "proto3";
package contacts; // package 是一个可选的声明符, 声明其命名空间
// 引入 any.proto 文件
import "google/protobuf/any.proto";
// 地址
message Address
{
string home_address = 1; // 家庭地址
string unit_address = 2; // 单位地址
}
// 定义联系人message
message PeopleInfo
{
string name = 1; // 姓名
int32 age = 2; // 年龄
message Phone
{
string number = 1; // 电话号码
enum PhoneType
{
MP = 0; // 移动电话
TEL = 1; // 固定电话
}
PhoneType type = 2;
}
repeated Phone phone = 3; // 电话
// 新增字段
google.protobuf.Any data = 4;
}
// 通讯录:修改消息名为ContactBook(大驼峰,避免和字段名contacts冲突)
message ContactBook
{
repeated PeopleInfo contacts = 1;
}
更新内容如下所示:

然后进行编译:
bash
protoc --cpp_out=. contacts.proto
在 contacts.pb.h 代码中,对于 Any 类型字段:
- 设置和获取:获取方法的方法名称与小写字段名称完全相同。设置方法可以使用
mutable_方法,返回值为 Any 类型的指针,这类方法会为我们开辟好空间,可以直接对这块空间的内容进行修改。
之前讲过,我们可以在 Any 字段中存储任意消息类型,这就要涉及到任意消息类型和 Any 类型的互转。这部分代码就在 Google 为我们写好的头文件 any.pb.h 中。
对 any.pb.h 部分代码展示:
cpp
class PROTOBUF_EXPORT Any final : public ::PROTOBUF_NAMESPACE_ID::Message {
bool PackFrom(const ::PROTOBUF_NAMESPACE_ID::Message& message) {
...
}
bool UnpackTo(::PROTOBUF_NAMESPACE_ID::Message* message) const {
...
}
template<typename T> bool Is() const {
return _impl_.any_metadata_.Is<T>();
}
};
解释:
- 使用
PackFrom()方法可以将任意消息类型转为 Any 类型。 - 使用
UnpackTo()方法可以将 Any 类型转回之前设置的任意消息类型。 - 使用
Is()方法可以用来判断存放的消息类型是否为typename T。
2.2 更新 write.cc 文件
代码如下:
cpp
#include <iostream>
#include <fstream>
#include "contacts.pb.h"
using namespace std;
using namespace contacts; // 把命名空间展开
// 新增联系人
void AddPeopleInfo(PeopleInfo *people)
{
cout << "-------------新增联系人-------------" << endl;
cout << "请输入联系人姓名: ";
string name;
getline(cin, name);
people->set_name(name);
cout << "请输入联系人年龄: ";
int age;
cin >> age;
people->set_age(age);
cin.ignore(256, '\n'); // 清除回车
for (int i = 0;; i++)
{
cout << "请输入联系人电话" << i + 1 << " (输入回车即可完成电话新增): ";
string number;
getline(cin, number);
if (number.empty())
{
break;
}
PeopleInfo_Phone *phone = people->add_phone();
phone->set_number(number);
cout << "请输入该电话类型 (1、移动电话 2、固定电话): ";
int type;
cin >> type;
cin.ignore(256, '\n'); // 清除回车
switch (type)
{
case 1:
phone->set_type(PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_MP); // 设置移动电话
break;
case 2:
phone->set_type(PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_TEL); // 设置固定电话
break;
default:
cout << "选择有误! " << endl;
break;
}
}
Address address; // 定义一个地址对象
cout << "请输入联系人家庭地址: ";
string homeAddress;
getline(cin, homeAddress);
address.set_home_address(homeAddress);
cout << "请输入联系人单位地址: ";
string unitAddress;
getline(cin, unitAddress);
address.set_unit_address(unitAddress);
// 把Address类型的对象转化为Any类型
people->mutable_data()->PackFrom(address);
cout << "-----------添加联系人成功-----------" << endl;
}
int main()
{
GOOGLE_PROTOBUF_VERIFY_VERSION;
ContactBook contacts;
// 读取本地已存在的通讯录文件
fstream input("contacts.bin", ios::in | ios::binary);
if (!input)
{
cout << "contacts.bin not find, create new file!" << endl;
}
else if (!contacts.ParseFromIstream(&input))
{
cerr << "parse error!" << endl;
input.close();
return -1;
}
// 向通讯录中添加一个联系人
AddPeopleInfo(contacts.add_contacts());
// 将通讯录写入本地文件中
fstream output("contacts.bin", ios::out | ios::trunc | ios::binary);
if (!contacts.SerializeToOstream(&output))
{
cerr << "write error!" << endl;
input.close();
output.close();
return -1;
}
cout << "write success!" << endl;
input.close();
output.close();
google::protobuf::ShutdownProtobufLibrary();
return 0;
}
更新内容如下:

2.3 更新 read.cc 文件
代码如下:
cpp
#include <iostream>
#include <fstream>
#include "contacts.pb.h"
using namespace std;
using namespace contacts; // 把命名空间展开
// 打印联系人列表
void PrintContacts(ContactBook& contacts)
{
for (int i = 0; i < contacts.contacts_size(); i++)
{
cout << "---------------联系人" << i+1 << "---------------" << endl;
const PeopleInfo& people = contacts.contacts(i);
cout << "联系人姓名: " << people.name() << endl;
cout << "联系人年龄: " << people.age() << endl;
for (int j = 0; j < people.phone_size() ; j++)
{
// 打印联系人电话
const PeopleInfo_Phone& phone = people.phone(j);
cout << "联系人电话" << j+1 << ": " << phone.number(); // 这里不需要换行符
// 打印联系人电话类型
cout << " (" << phone.PhoneType_Name(phone.type()) << ")" << endl;
}
// 如果data有数据 并且 data的数据类型是Address, 那么就打印联系人地址
if (people.has_data() && people.data().Is<Address>())
{
Address address;
people.data().UnpackTo(&address);
if (!address.home_address().empty())
{
cout << "联系人家庭地址: " << address.home_address() << endl;
}
if (!address.unit_address().empty())
{
cout << "联系人单位地址: " << address.unit_address() << endl;
}
}
}
}
int main()
{
GOOGLE_PROTOBUF_VERIFY_VERSION;
ContactBook contacts;
// 读取本地已存在的通讯录文件(以二进制方式读取)
fstream input("contacts.bin", ios::in | ios::binary);
if (!contacts.ParseFromIstream(&input))
{
cerr << "parse error!" << endl;
input.close();
return -1;
}
// 打印通讯录列表
PrintContacts(contacts);
google::protobuf::ShutdownProtobufLibrary();
return 0;
}
更新内容如下:

2.4 编译运行
当你编译运行以后,如果提示报错:
bash
[edison@vm-centos:~/myCode/proto3]$ make
g++ -o write write.cc contacts.pb.cc -std=c++11 -lprotobuf
/tmp/cc4zOG7m.o: In function `google::protobuf::Any* google::protobuf::MessageLite::CreateMaybeMessage<google::protobuf::Any>(google::protobuf::Arena*)':
write.cc:(.text._ZN6google8protobuf11MessageLite18CreateMaybeMessageINS0_3AnyEEEPT_PNS0_5ArenaE[_ZN6google8protobuf11MessageLite18CreateMaybeMessageINS0_3AnyEEEPT_PNS0_5ArenaE]+0x14): undefined reference to `google::protobuf::Any* google::protobuf::Arena::CreateMaybeMessage<google::protobuf::Any>(google::protobuf::Arena*)'
collect2: error: ld returned 1 exit status
make: *** [write] Error 1
那么需要在 write.cc 和 read.cc 文件中添加以下代码,放在所有 #include 语句的下方,main 函数之前。
cpp
// --- 强行补齐缺失的 Arena 模板实现 ---
#include <google/protobuf/any.pb.h>
namespace google
{
namespace protobuf
{
template <>
Any *Arena::CreateMaybeMessage<Any>(Arena *arena)
{
return Arena::CreateMessageInternal<Any>(arena);
}
}
}
// ----------------------------------
此时 make 编译以后,新增一个联系人。

此时就完成了。