【问题标题】:In C++, how to create a map whose value is a protobuf extension indentifier在 C++ 中,如何创建一个值为 protobuf 扩展标识符的映射
【发布时间】:2013-11-27 08:12:22
【问题描述】:

在 protobuf 中,我们有几个选项来实现继承。 “嵌套扩展”就是其中之一: http://www.indelible.org/ink/protobuf-polymorphism/

这里有趣的是如何读取序列化文件。我们必须创建一个 Map 以将 Animal.type 与其扩展标识符对应,以便将动物转换为正确的 Dog 或 Cat。但是,在上面网站提供的示例中,使用的语言是 Python。这意味着可以在不指定键类型或值类型的情况下初始化 Map。而且效果很好:

# Unpack the serialized bytes.
animal = Animal()
animal.ParseFromString(bytes)

# Determine the appropriate extension type to use.
extension_map = { Animal.Cat: Cat.animal, Animal.Dog: Dog.animal }
extension = animal.Extensions[extension_map[animal.type]]

但是,为了在 C++ 中实现这样的映射,键类型和值类型是强制性的。那么,我应该使用什么类型的值才能将两个不同的扩展标识符存储到同一个映射中?

Map<Animal::Type, ::google::protobuf::internal::ExtensionIdentifier>?

很遗憾,这显然行不通。

我还将在这里复制粘贴写作范例: 从动物_pb2进口*

# Construct the polymorphic base message type.
animal = Animal()
animal.type = Animal.Cat

# Create the subclass type by referencing the appropriate extension type.
# Note that this uses the self-referential field (Cat.animal) from within
# nested message extension.
cat = animal.Extensions[Cat.animal]
cat.declawed = True

# Serialize the complete message contents to a string.  It will end up
# looking roughly like this: [ type [ declawed ] ]
bytes = animal.SerializeToString()

Extensions() 函数可以让我们使用扩展的标识符来获取它的扩展。

【问题讨论】:

    标签: c++ inheritance map key protocol-buffers


    【解决方案1】:

    如果我对问题的理解正确,您希望将构建的扩展对象的映射存储在映射中,以便在解析消息后访问。

    在这种情况下,boost 中有 variantany 类型,poco libraries 等等。您可以将键类型固定(即枚举类型或字符串)并将值类型设置为变体类型:

    #include <boost/any.hpp>
    #include <map>
    #include <iostream>
    #include <string>
    #include <memory>
    
    struct Animal {
        std::string type;
    };
    
    struct Dog : public Animal {
        constexpr static const char* TYPE = "Dog";
        void bark() const {
            std::cout<<"bow"<<std::endl;
        }
    };
    
    struct Cat : public Animal {
        constexpr static const char* TYPE = "Cat";
        void meow() const {
            std::cout<<"meow"<<std::endl;
        }
    };
    

    从消息中获取扩展的工厂:

    template <typename Animal,typename Message>
    std::unique_ptr<Animal> get_extension(Message m) {
        try {
            Animal a( boost::any_cast<Animal>(m[Animal::TYPE]) );
            //for simplicity
            return std::unique_ptr<Animal>(new Animal(a));
        } catch (...) {
            return std::unique_ptr<Animal>();
        }    
    }
    

    及用法:

    int main()
    {
        // simulation of a protobuf message
        std::map<std::string,boost::any> m;
        m[Dog::TYPE] = Dog();
        m[Cat::TYPE] = Cat();
    
        // the generic interface to the message extensions
        auto dog = get_extension<Dog>(m);
        if (dog)
            dog->bark();
        auto cat = get_extension<Cat>(m);
        if (cat)
            cat->meow();
    }
    

    compile@coliru

    更新:version 2 带有动物通用界面

    实际的任务可能是您有一条带有扩展名的消息,并且您希望动态创建对象。在第二个版本中,您还可以通过相同的界面“与动物交谈”:

    struct Animal {
        virtual void speak() const = 0;
        virtual ~Animal(){}
    };
    
    struct Dog : public Animal {
        constexpr static const char* TYPE = "Dog";
        virtual void speak() const {
            std::cout<<"bow"<<std::endl;
        }
    };
    // ... etc
    

    类型化反序列化动物的简单工厂:

    struct AnimalRegistry {
        std::map<std::string , std::function<std::unique_ptr<Animal>()>> creators;
    
        template<typename A>
        void register_creator() {
            creators[A::TYPE] = []() { return std::unique_ptr<Animal>(new A); };
        }
    
        template<typename Message>
        std::unique_ptr<Animal> create(Message m) {
            return creators[m.animal_type]();
        }
    };
    

    而且用法会略有不同:

    int main()
    {
        AnimalRegistry registry;
        registry.register_creator<Dog>();
        registry.register_creator<Cat>();
    
        Message received_message { "Dog" /*this is a part of your protobuf*/ };
    
        auto dog = registry.create(received_message);
        if (dog)
            dog->speak();
    
        Message another_message { "Cat" };
        auto cat = registry.create(another_message);
        if (cat)
            cat->speak();
    }
    

    【讨论】:

    • 如果你想得到一个 Dog 实例,你必须从 boost::any 转换为 Dog。这意味着您应该已经知道 boost::any 类型对应的确切类型。例如,如果您读取其他人创建的对象,您几乎不知道应该将 boost::any 类型转换为哪种类型。在我提供的示例中,它可以使用 ExtensionIdentifier 和 Extensions() 函数自动转换对象。
    • @pptime 确切地说,c++ 编译时定义的类型。在 c++ 中,如果要使用类型的接口,则需要知道进行调用的类型。我没有看到您的评论与我的解决方案主张相矛盾。我将尝试展示一个简单的工厂代码,以便您明白我的意思。
    • 正如您试图评论的那样:是的,您需要知道“狗”是“狗”类型。那你怎么称呼这个方法'Dog::bark'呢?这是一种静态类型语言。如果要使用单个接口,请使用 Animal 作为接口并根据注册的类型动态创建对象。与您的需求仍然没有矛盾。
    • 这里是 (s. coliru) 另一个例子,仍然没有“如果狗然后狗如果猫然后猫”。 'ifs' 检查消息转换是否成功,而不检查类型。它是从消息本身推断出来的。
    • 你是对的。转换成功只是意味着它是狗类型,而不是猫类型,对吗?你是对的,无论如何我们应该知道我们处理的所有类型才能使用它。
    猜你喜欢
    • 1970-01-01
    • 2012-05-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多