首页 > 解决方案 > 与任何类型的函数的接口

问题描述

我想为消息泵定义一个接口,该接口能够发送和接收具有用户指定类型的消息,以便在生产者和消费者之间进行通信。

目前我已经这样做了:

template <typename Message>
struct message_pump
{
    virtual void send(Message &&) = 0;

    //! Blocks if no message is available.
    virtual Message receive() = 0;
};

然后我想将此message_pump接口用作active类的成员(Herb Sutters 的模式 - “Prefer Using Active Objects 而不是 Naked Threads”):

template <typename Message>
class active
{
  private:
    struct quit_message{};
    using MessageProxy = typename std::variant<Message, quit_message>;

    std::unique_ptr<message_pump<MessageProxy>> message_pump_impl;
    std::function<void(Message&&)> message_handler;
    std::thread worker_thread;

    void thread_code() {
        while(true)
        {
            auto m{message_pump_impl->receive()};
            if(std::holds_alternative<quit_message>(m))
                break;
            message_handler(std::move(std::get<Message>(m)));
        }
    }

  public:
    active(std::unique_ptr<message_pump<MessageProxy>> message_pump_impl,
           std::function<void(Message&&)> message_handler) : 
               message_pump_impl{std::move(message_pump_impl)},
               message_handler{message_handler},
               worker_thread{[this](){ this->thread_code(); }} {}

};

message_pump这里的问题是静态和动态多态性不能很好地混合,并且在不知道底层的类型的情况下不可能注入实现Message

我做这active门课的原因是我想在各种 RTOS 中重用它,它们提供不同queuethread类实现,并且仍然能够在本地计算机上测试它。(我在这个清单中std::thread只是为了简化,因为如何使thread实现可注入是一个不同的主题)。

问题是定义接口的首选方式 - 最 OOP 和“应该做的”方式 - 以便message_pump能够轻松地将实现注入active类?

我有几个解决方案:

  1. struct message {}在内部定义message_pump并创建MessageProxy一个从message_pump::message. 然后std::unique_ptr<message>receive()接口函数返回。

  2. 使用std::any而不是Message里面MessagePump

  3. 使用静态多态性并message_pump通过模板参数注入实现。然后message_pump不需要显式定义接口,如果实现者没有特定的功能,我们将得到编译器错误。

  4. 使用 C++20 概念?(我也想知道如何用 C++17 解决它)。

  5. 混合 Ad.4 和 Ad.5:使用模板参数,但明确定义它应实现的接口。

  6. 其他?

标签: c++interfacepolymorphismc++17c++-concepts

解决方案


  1. 使用动态多态性。这是可行的,但是,它需要active类型公开它在内部使用什么类型来保存消息,以便可以构造正确类型的队列。

  2. struct message {}在内部定义message_pump并创建MessageProxy一个从message_pump::message. 如果我们使用动态多态性,这不会给我们带来任何好处。但是,如果使用静态多态性,用户消息可以派生自 active::message哪个可以派生自message_pump::message,这样就可以在没有“emplace”方法的情况下添加不可移动的消息。必须从中派生消息active::message是一个不应该被忽视的缺点,特别是如果消息可以重用现有类型,例如int其他类型。即使对于通常不需要这样做的队列,这也需要动态分配消息。

  3. 使用std::any而不是Message里面MessagePump。如果使用动态多态性,它解决了active必须公开它在内部使用的消息类型并允许active不是模板的问题。但是会丢失静态类型检查并具有运行时开销。我不建议这样做,因为静态类型检查可以使重构更不容易出错。

  4. 使用静态多态性并message_pump通过模板参数注入实现。如果message_pump是模板模板参数,active则不必公开它在内部使用的消息类型。这类似于标准库采用的方法。但是,错误消息可能难以理解。

  5. 使用 C++20 概念?(我也想知道如何用 C++17 解决它)。概念可以帮助记录message_pump需要哪些方法,并可能给出更好的错误。我不会用 c++17 尝试这样的事情,因为 c++17 版本往往难以阅读并且在这种情况下几乎没有什么好处。

  6. 使用模板参数,但明确定义它应该实现的接口。基本上是什么概念是要实现的。

  7. 其他?实现一个可在多个平台上工作的队列,可能使用#ifdef并已使用该队列或该队列的active默认模板参数。activemessage_pump


推荐阅读