C++Protobufs::如何使用MergeFrom()擦除特定字段
C++ Protobufs :: How to erase particular field with MergeFrom()?
首先:我不是protobuf方面的专家。
假设我有这样的消息结构:
package msg_RepAndOpt;
message RepAndOpt
{
repeated string name = 1;
optional string surname = 2;
...
// there are lots of others.
}
我有两个组件有这个消息的副本:
// component1:
RepAndOpt A;
A.add_name("Name");
A.set_surname("Surname");
// component2:
RepAndOpt B;
在我的例子中,组件通过事务机制修改这些消息。这意味着,如果一个组件更改了某个字段,它也会将其发送到另一个组件以传播这些更改。组件接收器正在进行合并:
// Component2 modifies B and sends it to component1.
// Component1 perfoms merge:
A.MergeFrom(B);
现在,比如component2想要擦除字段"name"。如果它将发送清除B消息(默认结构),则:
- MergeFrom()不会修改A
- CopyFrom()还会擦除其他字段
另一种方法是用A的内容填充B,清除名称字段,component1将使用CopyFrom()。但这是不可接受的,因为系统负载非常高,而且可能还有很多其他字段。因此,清除名称字段所需的解决方案是:
- 组件2创建B消息
- 显式存储只想擦除名称字段的信息
- 组件1执行A.MergeFrom(B)
- 结果:A::name被清除,但其他字段保持不变
就我测试的情况而言,这适用于重复字段和可选字段。有现成的解决方案吗?或者我应该修改protobuf实现?
您的案例没有内置的protobuf解决方案。显而易见的解决方案是迭代消息A中的所有字段,并检查该字段是否存在于消息B中,如果不存在,则可以清除它
使用基本的MergeFrom()
无法解决此问题,但您可能需要从protobuf库中查看这些:
https://github.com/google/protobuf/blob/master/src/google/protobuf/field_mask.protohttps://github.com/google/protobuf/blob/master/src/google/protobuf/util/field_mask_util.h
特别是FieldMaskUtil::MergeMessageTo()
似乎可以随心所欲。您需要构造一个FieldMask
,准确地指定您感兴趣的字段,这样其他字段就不会受到影响。
UPD:在Kenton Varda发表评论后更新(见下文)。
扩展前面的答案之一:
有一种方法可以通过在消息定义中添加新字段来解决这个问题(这适用于proto v2):
repeated int32 fields_to_copy = 15;
此字段将由接收方将复制(而不是合并)的字段的ID填充。
我还实现了这个助手功能:
// CopiableProtoMsg.hpp
#pragma once
#include <google/protobuf/message.h>
template <typename T>
void CopyMessageFields(const T& from, T& to)
{
const ::google::protobuf::Descriptor *desc = T::descriptor();
const ::google::protobuf::Reflection *thisRefl = from.GetReflection();
std::vector<const ::google::protobuf::FieldDescriptor*> fields;
int size = from.fields_to_copy_size();
for (int i = 0; i < size; ++i)
{
const ::google::protobuf::FieldDescriptor *field = desc->FindFieldByNumber(from.fields_to_copy(i));
fields.push_back(field);
}
T msgCopy(from);
thisRefl->SwapFields(&to, &msgCopy, fields);
to.clear_fields_to_copy();
}
此函数检查fields_to_copy字段并执行复制(通过SwapFields())。
这是一个简单的测试:
RepAndOpt emulateSerialization(const RepAndOpt& B)
{
RepAndOpt BB;
std::string data;
B.SerializeToString(&data);
BB.ParseFromString(data);
return BB;
}
TEST(RepAndOptTest, additional_field_do_the_job_with_serialization)
{
RepAndOpt A;
RepAndOpt B;
A.add_name("1");
A.add_name("2");
A.add_name("3");
A.set_surname("A");
B.add_name("1");
B.add_name("3");
B.add_fields_to_copy(RepAndOpt::kNameFieldNumber);
RepAndOpt recvB = emulateSerialization(B);
A.MergeFrom(recvB);
CopyMessageFields(recvB, A);
EXPECT_EQ(2, A.name_size());
EXPECT_STREQ("1", A.name(0).c_str());
EXPECT_STREQ("3", A.name(1).c_str());
EXPECT_TRUE(A.has_surname());
EXPECT_EQ(0, A.fields_to_copy_size());
}
用字段掩码扩展方法(由Kenton Varda提出):
注意:这个解决方案需要proto3,但是原始消息可以用proto2语法声明。(链接到证明)
我们可以定义一个字段掩码字段:
import "google/protobuf/field_mask.proto";
message RepAndOpt
{
repeated string name = 1;
optional string surname = 2;
optional google.protobuf.FieldMask field_mask = 3;
}
下面是测试用法:
RepAndOpt emulateSerialization(const RepAndOpt& B)
{
RepAndOpt BB;
std::string data;
B.SerializeToString(&data);
BB.ParseFromString(data);
return BB;
}
void mergeMessageTo(const RepAndOpt& src, RepAndOpt& dst)
{
dst.MergeFrom(src);
if (src.has_field_mask())
{
FieldMaskUtil::MergeOptions megreOpt;
megreOpt.set_replace_message_fields(true);
megreOpt.set_replace_repeated_fields(true);
FieldMaskUtil::MergeMessageTo(src, src.field_mask(), megreOpt, &dst);
}
}
TEST(RepAndOptTest, fix_merge_do_the_job_with_serialization_multiple_values)
{
RepAndOpt A;
A.add_name("A");
A.add_name("B");
A.add_name("C");
A.set_surname("surname");
RepAndOpt B;
B.add_name("A");
B.add_name("C");
B.mutable_field_mask()->add_paths("name");
mergeMessageTo(emulateSerialization(B), A);
EXPECT_EQ(2, A.name_size());
EXPECT_STREQ("A", A.name(0).c_str());
EXPECT_STREQ("C", A.name(1).c_str());
EXPECT_STREQ("surname", A.surname().c_str());
}
我有类似的用例,并最终基于@Denis的答案实现了我自己的混合。
尽管语言是Golang,它没有带MergeOptions的FieldMaskUtil。
RepAndOpt.A.name RepAndOpt.B.name
remove ["A", "B", "C"] ["A"] => remove: A, keep: B, C
add ["A", "B", "C"] ["D"] => add: D, keep: A, B, C
add/remove ["A", "B", "C"] ["A", "D"] => remove: A, add: D, keep: B
- 引用一个已擦除类型(void*)的指针
- 擦除while循环中迭代的元素
- 在运行时处理类型擦除的数据-如何不重新发明轮子
- C++擦除(如果存在)
- 将结构字段的类型展开为可变模板参数
- 将位字段导出到数组
- 为了方便起见,我应该避免公开私有字段变量吗
- 在映射擦除c++期间执行循环的次数
- 当字段可以为null时,如何使用C++接口在Avro中写入数据
- 为什么擦除方法会影响结束方法
- 在java中读取c++字节的位字段
- 链接器找不到在虚拟类 c++ 中访问的静态字段的符号
- 私有字段对象与私有继承?
- 声明没有默认构造函数的字段
- C++内存模型和位字段的最大序列
- 声明为无效的变量或字段'...' Ardunio 编译器上的错误
- C++ 字符串类擦除成员函数的时空复杂性
- 矢量2D擦除元素并保持字段为空
- C++Protobufs::如何使用MergeFrom()擦除特定字段
- 除一个字段外的所有字段的c++隐式复制