C++ C++;Protobufs::如何使用MergeFrom()删除特定字段?
首先:我不是protobuf方面的专家 假设我有这样的消息结构:C++ C++;Protobufs::如何使用MergeFrom()删除特定字段?,c++,protocol-buffers,C++,Protocol Buffers,首先:我不是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.
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()不会修改一个李>
- CopyFrom()还将删除其他字段
是否有现成的解决方案,或者我应该修改protobuf实现?您的案例中没有内置的protobuf解决方案。显而易见的解决方案是迭代消息A中的所有字段,并检查该字段是否存在于消息B中,如果不存在,您可以将其清除。使用basic
MergeFrom()
无法解决此问题,但您可能希望从protobuf库中检出这些字段:
尤其是
FieldMaskUtil::MergeMessageTo()
似乎能满足您的需求。您需要构造一个字段掩码
,精确指定您感兴趣的字段,以便其他字段保持不变。UPD:在Kenton Varda的评论后更新(见下文)
展开前面的答案之一:
有一种方法可以通过在消息定义中添加新字段来解决此问题(这适用于proto v2):
此字段将由将在接收器端复制(未合并)的字段ID填充
我还实现了这个helper函数:
// 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();
}
使用场掩码扩展方法(由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());
}
将
B.name
设置为某个特殊值(“DELETE_ME”),进行正常合并,然后扫描所有字段并删除其值为特殊值的字段如何?是的。这是一个可能的解决办法。好消息是它不需要在protobuf实现中进行更改。但我也应该为其他类型选择这样的特殊值,例如int32。这里更难。它将是一个大的开关,检测字段的类型,然后与特殊值进行比较。是的,对于您希望支持的每种类型,您都需要一个这样的值。重载应该提供一种简单的编程方法。但在这种情况下,如果我发送clear B消息,A的所有字段都将被清除,对吗?但我只想清除一个字段。是的,没有办法将字段标记为要清除。如果您可以完全控制消息类型,则可以添加一个重复字符串字段,其中包含要清除的字段。是的,但我已经考虑过需要从重复字段中删除某些值的情况。没有完全抹掉它。假设我有{1,2,3},我想删除{2}。在这种情况下,您需要像更改集这样的东西来告诉接收组件需要做什么。如果两个组件可以同时更改和/或不完全同步,我看不出有任何干净的方法来处理您的案例。嗨,肯顿,我不知道如何使用它。你能给我举个小例子吗?好的。我设法使用了它:在.proto文件中,我添加了:google.protobuf.FieldMask field\u mask=3代码>在发送方(.cpp):'B.可变字段\掩码()->添加\路径(“名称”);'在接收器端(*.cpp):“如果(B.有字段掩码()){FieldMaskUtil::MergeOptions megreOpt;megreOpt.设置替换重复字段(true);FieldMaskUtil::MergeMessageTo(B,B.字段掩码(),megreOpt,&A);”,这需要proto v3@肯顿,请用下面的proto v2检查我的版本。不要覆盖MergeFrom()
。您将破坏protobuf实现中希望MergeFrom()
以某种方式工作的部分。相反,将基于反射的代码作为一个独立函数——没有理由特别需要将它放在MergeFrom()
方法中,因为可以让调用此函数的代码调用新函数。也就是说,你根本不需要用户继承。对!谢谢你,肯顿!
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());
}