【问题标题】:Runtime type selection based on map of types基于类型映射的运行时类型选择
【发布时间】:2019-12-02 10:12:42
【问题描述】:

背景

我正在编写一个使用 USB 设备的应用程序。这包括我的应用程序可以使用的 USB 设备的设备发现,基于 USB 供应商 ID 和产品 ID。然而,这些设备有时有多个可能工作的驱动程序,即应用程序实现,具体取决于平台和月相(客户遗留的东西)。所以我想通过std::shared_ptr 使用运行时多态性和一系列不错的接口和东西。

问题

我不知道如何根据运行时给定的键来make_shared 某种类型的对象。至少在不丑陋的意义上不是。

目前的解决方案

我正在考虑以某种方式将类型值存储到映射 known_drivers 中(在此示例中是多映射,但差别不大),以便最终根据数值我可以构造不同的类类型和东西将其转换为shared_ptr

示例代码

#include <iostream>
#include <algorithm>
#include <iterator>
#include <map>
#include <memory>
#include <stdexcept>

using vendor_product_usb_id_t = std::pair<uint16_t, uint16_t>;

struct usb_device {
    static std::shared_ptr<usb_device> open(vendor_product_usb_id_t);
    virtual void do_stuff() = 0;
};

struct usb_device_using_driver_a : usb_device {
    usb_device_using_driver_a() {throw std::runtime_error("Not supported on this platform");}
protected:
    void do_stuff() override {}
};

struct usb_device_using_driver_b : usb_device {
protected:
    void do_stuff() override {std::cout << "Stuff B\n";}
};


const std::multimap<vendor_product_usb_id_t, ??> known_drivers = {{{0x42,0x1337}, driver_a}, {{0x42,0x1337}, driver_b}};

std::shared_ptr<usb_device> usb_device::open(vendor_product_usb_id_t id) {
    std::shared_ptr<usb_device> value;
    for (auto [begin,end] = known_drivers.equal_range(id); begin != end; ++begin) {
        try {
            value = std::make_shared<*begin>();
        } catch (std::exception& e) {
            continue;
        }
    }
    return value;
}

int main() {
    auto device = usb_device::open(std::make_pair(0x42,0x1337));
    if (device) {
        device->do_stuff();
    }
}

【问题讨论】:

  • 我想你要搜索的词是factory pattern?!除此之外,我没有看到任何问题......
  • 问题是,我不想在地图中存储已经构建的对象,如果这些元组匹配,应该只是稍后尝试构建一个的建议。如果在运行时添加更多值(即可能的驱动程序),那么大的 switch case 将无法工作
  • 我仍然无法阅读问题。我的理解是:在给定的键上创建一些派生类型。这就是典型的工厂模式。内部实现可以基于手工制作的 switch case,但在模板类型列表上更聪明。有数以百万计的现成可用的实现。但再说一遍:你的问题是什么。
  • 类似const std::multimap&lt;vendor_product_usb_id_t, std::function&lt;std::shared_ptr&lt;usb_device&gt;()&gt;&gt; known_drivers

标签: c++ c++17 run-time-polymorphism


【解决方案1】:

在 C++ 中无法存储类型并使用它来创建实例,因为 C++ 还没有反射。

不过,factory patternvirtual constructor idiom 可以满足您的需求。

作为一个基本的起点:

using vendor_product_usb_id_t = std::pair<uint16_t, uint16_t>;

struct usb_device {
    using usb_ptr = std::shared_ptr<usb_device>;

    virtual void do_stuff() = 0;

    // virtual constructor
    virtual usb_ptr create(vendor_product_usb_id_t) = 0;
};


struct usb_device_using_driver_a : usb_device {
    usb_ptr create(vendor_product_usb_id_t) override {
        return usb_ptr(new usb_device_using_driver_a);
    }

    void do_stuff() override {
        std::cout << "usb_device_using_driver_a::do_stuff()" << std::endl;
    }
};


struct usb_device_using_driver_b : usb_device {
    usb_ptr create(vendor_product_usb_id_t) override {
        throw std::runtime_error("Not supported on this platform");
    }

    void do_stuff() override {
        std::cout << "usb_device_using_driver_b::do_stuff()\n";
    }
};


class usb_device_factory {
public:

    static usb_device::usb_ptr open(vendor_product_usb_id_t id) {
        // note this map is static
        // for simplicity changed the multimap to map
        static std::map<vendor_product_usb_id_t, usb_device::usb_ptr> known_drivers = {
            std::make_pair(std::make_pair(0x42,0x1337), usb_device::usb_ptr(new usb_device_using_driver_a())),
            std::make_pair(std::make_pair(0x43,0x1337), usb_device::usb_ptr(new usb_device_using_driver_b())),
        };

        return known_drivers[id]->create(id);
    }
};


int main() {
    try {
        auto device = usb_device_factory::open(std::make_pair(0x42,0x1337));
        device->do_stuff();

        // this will throw
        device = usb_device_factory::open(std::make_pair(0x43,0x1337));
        device->do_stuff();
    }
    catch(std::exception const& ex) {
        std::cerr << ex.what();
    }
}

Live.

不使用虚构造函数也可以,但工厂模式的基本原理仍然适用:

using vendor_product_usb_id_t = std::pair<uint16_t, uint16_t>;


struct usb_device {
    virtual void do_stuff() = 0;
};

using usb_ptr = std::shared_ptr<usb_device>;



struct usb_device_using_driver_a : usb_device {
    void do_stuff() override {
        std::cout << "usb_device_using_driver_a::do_stuff()" << std::endl;
    }
};


struct usb_device_using_driver_b : usb_device {
    void do_stuff() override {
        std::cout << "usb_device_using_driver_b::do_stuff()\n";
    }
};


class usb_device_factory {
public:

    static usb_ptr open(vendor_product_usb_id_t id) {
        // note this map is static
        static std::map<vendor_product_usb_id_t, std::function<usb_ptr()>> known_drivers = {
            std::make_pair(std::make_pair(0x42,0x1337), []() { return usb_ptr(new usb_device_using_driver_a()); } ),
            std::make_pair(std::make_pair(0x43,0x1337), []() { return usb_ptr(new usb_device_using_driver_b()); } ),
        };

        return known_drivers[id]();
    }
};


int main() {
    try {
        auto device = usb_device_factory::open(std::make_pair(0x42,0x1337));
        device->do_stuff();

        device = usb_device_factory::open(std::make_pair(0x43,0x1337));
        device->do_stuff();
    }
    catch(std::exception const& ex) {
        std::cerr << ex.what();
    }
}

Live.

使用这两种变体,可以在运行时使用额外的驱动程序扩展地图。由于它存储在“动态”地图中,因此没有限制驱动程序数量的开关。

编辑:

刚刚发现了一个类似的问题,答案和我的基本一样,unsing factory pattern with virtual constructors:

Can objects be created based on type_info?

【讨论】:

  • 叹息,我希望有一些更好看的 RTTI 技巧,但我想我坚持我已经想到的 std::function 值类型映射,你也使用过。还是谢谢!
猜你喜欢
  • 2016-08-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-28
  • 1970-01-01
  • 1970-01-01
  • 2012-02-20
  • 2022-08-03
相关资源
最近更新 更多