协议缓冲区GetRepeatedField(反射)代码优化

时间:2015-03-11 07:55:01

标签: c++ templates reflection protocol-buffers

我使用协议缓冲区的反射功能在运行时读取消息字段值。

原型我:

package xapp.battle;
    message BATTLE_DATA {
        repeated AInfo aInfo = 1;
        repeated BInfo bInfo = 2;
        repeated CInfo cInfo = 3;
        // a lot other repeated messages
    }

    message AInfo {
        int32 test_field = 1;
        // ......
    }

    message BInfo {
        int32 test_field = 1;
        // ......
    }

    message CInfo {
        int32 test_field = 1;
        // ......
    }

我现在的代码:

void DO_SOMETHING(messageName) {

    const Descriptor* pDescriptor = BATTLE_DATA->GetDescriptor();
    const FieldDescriptor* pMessageField = pDescriptor->FindFieldByName(messageName);
    const Reflection* pReflection = BATTLE_DATA->GetReflection();

    if (messageName == "aInfo") {
        const RepeatedPtrField<::xapp::battle::AInfo> repeated_ptr_field = pReflection->GetRepeatedPtrField<::xapp::battle::AInfo>(*BATTLE_DATA, pMessageField);
        for (int i = 0; i < repeated_ptr_field.size(); i ++) {
            ::xapp::battle::AInfo messageInfo = repeated_ptr_field.Get(i);
            // continue to read the test_field value of messageInfo
        }
    }
    else if (messageName == "bInfo") {
        const RepeatedPtrField<::xapp::battle::BInfo> repeated_ptr_field = pReflection->GetRepeatedPtrField<::xapp::battle::BInfo>(*BATTLE_DATA, pMessageField);
        for (int i = 0; i < repeated_ptr_field.size(); i ++) {
            ::xapp::battle::BInfo messageInfo = repeated_ptr_field.Get(i);
            // continue to read the test_field value of messageInfo
        }
    }
    else if (messageName == "CInfo") {
        const RepeatedPtrField<::xapp::battle::CInfo> repeated_ptr_field = pReflection->GetRepeatedPtrField<::xapp::battle::CInfo>(*BATTLE_DATA, pMessageField);
        for (int i = 0; i < repeated_ptr_field.size(); i ++) {
            ::xapp::battle::CInfo messageInfo = repeated_ptr_field.Get(i);
            // continue to read the test_field value of messageInfo
        }
    }
    // ......
    else {
            LOG("loadOneBin - Unknown messageName");
    }
}

此代码有效,但显然它不是最好的解决方案,因为有太多的重复&#34; else-if&#34;代码块。

我想要的是(至少摆脱那些&#34;否则 - 如果&#34;块):

const RepeatedPtrField<::xapp::battle::MESSAGE_NAME> repeated_ptr_field = pReflection->GetRepeatedPtrField<::xapp::battle::MESSAGE_NAME>(*BATTLE_DATA, pMessageField);
for (int i = 0; i < repeated_ptr_field.size(); i ++) {
    ::xapp::battle::MESSAGE_NAME messageInfo = repeated_ptr_field.Get(i);
    // continue to read the test_field value of messageInfo

}

GetRepeatedPtrField的源代码:

template<typename PB>
inline const RepeatedPtrField<PB>& Reflection::GetRepeatedPtrField(
    const Message& message, const FieldDescriptor* field) const {
  return *static_cast<RepeatedPtrField<PB>* >(
      MutableRawRepeatedField(const_cast<Message*>(&message), field,
          FieldDescriptor::CPPTYPE_MESSAGE, -1,
          PB::default_instance().GetDescriptor()));
}

任何建议都将不胜感激,谢谢:)

1 个答案:

答案 0 :(得分:2)

正确答案

这里的正确答案是,您应该使用Reflection::GetRepeatedPtrField<T>()为重复字段的每个元素获取通用Reflection::GetRepeatedMessage(),而不是使用const Message&。不幸的是,您需要为每个元素调用此方法一次(使用Reflection::FieldSize()来查找大小)。

在每个Message上,您可以使用Message::GetDescriptor(),查找名为test_field的字段,然后查找Message::GetReflection()并使用该字段来读取该字段的值。作为优化,您可以安全地假设同一重复字段中的所有邮件都具有相同的DescriptorReflection个对象,因此您只需要获取这些对象并查找FieldDescriptor一次对于整个阵列。

像(未经测试)的东西:

int size = pReflection->FieldSize(*BATTLE_DATA, pMessageField);
Reflection* pInnerReflection = NULL;
FieldDescriptor* pTestFieldDesc = NULL;
for (int i = 0; i < size; i++) {
  const Message& msg = pReflection->GetRepeatedMessage(
      *BATTLE_DATA, pMessageField, i);
  if (pInnerReflection == NULL) {
    pInnerReflection = msg.GetReflection();
    pTestFieldDesc = msg.GetDescriptor()
        ->FindFieldByName("test_field");
  }
  int testField = pInnerReflection->GetInt32(msg, pTestFieldDesc);
  // ...
}

一个有趣但不好的答案

这第二个解决方案会稍微好一点,看起来更漂亮一点。不幸的是,它是技术未定义的行为。它将在所有编译器上实际工作,但除非遇到性能问题,否则您不应该这样做。我把它包括在这里是为了好玩,因为无论如何你都可能想出来,所以最好知道它为什么不好。

您可以致电GetRepeatedPtrField<google::protobuf::Message>(...)以获取RepeatedPtrField个通用Message个对象。然后,您可以像第一个解决方案一样获取描述符和反射,但不再需要调用Reflection::GetMessage(虚拟调用)来读取每条消息。 (但你仍然需要调用反射来阅读test_field。)

这是技术上未定义的行为,因为它假定了一些事情:

  • RepeatedPtrField<T>所有T的布局相同。这实际上是正确的,因为RepeatedPtrField<T>扩展RepeatedPtrFieldBase并且不添加任何新字段,但C ++标准并不要求这种情况。
  • 将特定消息类型指针(如AInfo*)向上转换为Message*不会更改指针的位。实际上,只要没有多重继承,并且Protobufs不使用多重继承(Google C ++样式指南甚至禁止它),所有编译器都是如此。
  • 编译器不会根据严格的别名规则进行优化,以便最终重新排序对RepeatedPtrField<Message>的访问权限,而不是对RepeatedPtrField<AInfo>(或其中任何类型)的其他访问权限。程序。在实践中,想象任何编译器实际为此用例执行此操作是不合理的,但在技术上允许C ++。