Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/135.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ C++;Protobufs::如何使用MergeFrom()删除特定字段?_C++_Protocol Buffers - Fatal编程技术网

C++ C++;Protobufs::如何使用MergeFrom()删除特定字段?

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.

首先:我不是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()不会修改一个
  • CopyFrom()还将删除其他字段
另一种方法是用一个clear name字段的内容填充B,component1将使用CopyFrom()。 但这是不可接受的,因为系统的负载非常高,可能还有很多其他字段。 因此,清除名称字段的理想解决方案是:

  • 组件2创建B消息
  • 显式存储它只想擦除名称字段的信息
  • 组件1执行A.MergeFrom(B)
  • 结果:A::name被清除,但其他字段保持不变
  • 就我测试而言,这适用于重复和可选字段。
    是否有现成的解决方案,或者我应该修改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());
    }