【问题标题】:C++ Protobufs :: How to erase particular field with MergeFrom()?C++ Protobufs :: 如何使用 MergeFrom() 擦除特定字段?
【发布时间】:2016-05-11 10:13:25
【问题描述】:

首先:我不是 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,清除名称字段,组件 1 将使用 CopyFrom()。 但这是不可接受的,因为系统负载非常高,可能还有很多其他领域。 因此,清理名称字段所需的解决方案是:

  1. Component2 创建 B 消息。
  2. 明确存储它只想擦除名称字段的信息。
  3. Component1 执行 A.MergeFrom(B)。
  4. 结果:A::name 已清除,但其他字段保持不变。

据我测试,这适用于重复和可选字段。 是否有任何现成的解决方案或者我应该修改 protobuf 实现?

【问题讨论】:

  • 如何将B.name 设置为某个特殊值(“DELETE_ME”),进行正常合并,然后扫描所有字段并删除那些值为特殊值的字段?
  • 是的。这是一个可能的解决方案。好消息是它不需要更改 protobuf 实现。但我也应该为其他类型选择这样的特殊值,例如 int32。这里更难。这将是一个大开关,检测字段的类型,然后与特殊值进行比较。
  • 是的,对于您希望支持的每种类型,您都需要一个这样的值。重载应该提供一种简单的编程方式。

标签: c++ protocol-buffers


【解决方案1】:

您的情况没有内置的 protobuf 解决方案。显而易见的解决方案是遍历消息 A 中的所有字段并检查该字段是否存在于消息 B 中,如果没有,您可以清除它。

【讨论】:

  • 但是在这种情况下,如果我发送清除B消息,A的所有字段都会被清除,对吗?但我只想清除一个字段。
  • 是的,没有办法将字段标记为要清除。如果您可以完全控制消息类型,则可以添加一个重复的字符串字段,其中包含您要清除的字段。
  • 是的,但是当我需要从重复字段中删除一些值时,我已经想到了这种情况。不能完全抹去。假设我有 {1,2,3},我想删除 {2}。
  • 对于这种情况,您需要更改集之类的东西来告诉接收组件需要做什么。如果两个组件可以同时更改和/或不完全保持同步,我认为没有干净的方法来处理您的案例。
【解决方案2】:

你不能用基本的MergeFrom() 解决这个问题,但你可能想从 protobuf 库中查看这些:

https://github.com/google/protobuf/blob/master/src/google/protobuf/field_mask.proto https://github.com/google/protobuf/blob/master/src/google/protobuf/util/field_mask_util.h

特别是FieldMaskUtil::MergeMessageTo() 似乎做你想做的事。您需要构造一个FieldMask,准确指定您感兴趣的字段,以便其他字段保持不变。

【讨论】:

  • 嗨 Kenton,不知道如何使用它。可以举个小例子吗?
  • 好的。我设法使用它:在 .proto 文件中我添加了:google.protobuf.FieldMask field_mask = 3; on sender side (.cpp) : 'B.mutable_field_mask()->add_paths("name");'在接收方 (*.cpp): 'if (B.has_field_mask()) { FieldMaskUtil::MergeOptions megreOpt; megreOpt.set_replace_repeated_fields(true); FieldMaskUtil::MergeMes​​sageTo(B, B.field_mask(), megreOpt, &A); }' 这需要 proto v3。 @Kenton,请使用下面的 proto v2 检查我的版本。
【解决方案3】:

UPD:在 Kenton Varda 的 cmets 之后更新(见下文)。

扩展之前的答案之一:

有一种方法可以通过在消息定义中添加新字段来解决它(这适用于 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());
}

【讨论】:

  • 不要覆盖MergeFrom()。您将破坏期望 MergeFrom() 以某种方式工作的部分 protobuf 实现。相反,将您的基于反射的代码作为一个独立的函数——没有理由特别需要在 MergeFrom() 方法中,因为您可以让调用它的代码改为调用新函数。同样,您根本不需要用户继承。
  • 对!谢谢你,@Kenton!
【解决方案4】:

使用字段掩码扩展方法(由 Kenton Varda 提出):

注意:此解决方案需要 proto3,但可以使用 proto2 语法声明原始消息。 (link to proof)

我们可以定义一个字段掩码字段:

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());
}

【讨论】:

    【解决方案5】:

    我有类似的用例,最终根据@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
    

    【讨论】:

      猜你喜欢
      • 2022-06-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-09
      • 2011-12-30
      • 2013-04-05
      • 2011-07-05
      相关资源
      最近更新 更多