【问题标题】:Interface with functions that take any type与任何类型的函数的接口
【发布时间】:2020-05-05 10:18:49
【问题描述】:

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

目前我是这样做的:

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 的模式——“更喜欢使用活动对象而不是裸线程”):

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 的类型的情况下不可能注入message_pump 的实现。

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

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

我有几个解决方案:

  1. message_pump 内定义struct message {} 并使MessageProxy 成为继承自message_pump::message 的结构。然后从receive()接口函数返回std::unique_ptr&lt;message&gt;

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

  3. 使用静态多态,通过模板参数注入message_pump实现。那么message_pump接口就不需要显式定义了,如果实现者没有特定的函数,我们会得到编译器错误。

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

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

  6. 其他?

【问题讨论】:

  • 一个问题,当std::variant 持有的类型之一是private struct quite_message 时,此类用户如何能够提供MessageProxy
  • 没错,这就是我要的:)
  • 我可以想象message_pump&lt;int&gt; 可能对某些 RTOS 有专长,所以我想知道 OOP 是否是最好的解决方案。但这对于quit_message 来说已经很棘手了。也许是catch(quit_message)
  • 好的,我想你可以在这里使用Type Erasure
  • 好的,我更新了我的答案,展示了如何使用 type erasureopaque pointers 来隐藏消息和消息泵的实现细节。

标签: c++ interface polymorphism c++17 c++-concepts


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

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

  3. MessagePump 中使用std::any 而不是Message。如果使用动态多态性,它解决了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 模板参数。

【讨论】:

  • 广告。 6 我怎样才能实现它?
【解决方案2】:

好的,您可以使用类型擦除和不透明指针来隐藏有关消息泵和消息的详细信息。

struct Message { std::string payload{ "Hello" }; };

struct VTable
{
    void* ( *Receive )( void* ptr );
    void  ( *Send )( void* ptr, void* message );
    void  ( *Destroy_ )( void* ptr );
    void* ( *Clone_ )( void* ptr );
    void* ( *MoveClone_ )( void* ptr );
};

template<typename T>
constexpr VTable VTableFor
{
    [ ]( void* ptr ) -> void* { return static_cast<T*>( ptr )->Receive( ); },
    [ ]( void* ptr, void* message ) { static_cast<T*>( ptr )->Send( message ); },
    [ ]( void* ptr ) { delete static_cast<T*>( ptr ); },
    [ ]( void* ptr ) -> void* { return new T{ *static_cast<T*>( ptr ) }; },
    [ ]( void* ptr ) -> void* { return new T{ std::move( *static_cast<T*>( ptr ) ) }; }
};


struct MessagePump 
{
    void* concrete_;
    const VTable* vtable_;

    template<typename T>
    MessagePump( T&& t ) 
        : concrete_( new T{ std::forward<T>( t ) } ),
          vtable_{ &VTableFor<T> } { }

    MessagePump( const MessagePump& rhs ) noexcept
        : concrete_{ rhs.vtable_->Clone_( rhs.concrete_ ) },
          vtable_{ rhs.vtable_ } { }

    MessagePump( MessagePump&& rhs ) noexcept
        : concrete_{ rhs.vtable_->MoveClone_( rhs.concrete_ ) },
          vtable_{ rhs.vtable_ } { }

    MessagePump& operator=( MessagePump rhs ) noexcept
    {
        swap( *this, rhs );      
        return *this;
    }

    friend void swap( MessagePump& lhs, MessagePump& rhs ) noexcept
    {
        using std::swap;
        swap( lhs.concrete_, rhs.concrete_ );
        swap( lhs.vtable_, rhs.vtable_ );
    }

    void* Receive( ) { return vtable_->Receive( concrete_ ); }

    void Send( void* message ) { vtable_->Send( concrete_, message ); }

    ~MessagePump( ) { vtable_->Destroy_( concrete_ ); }
};

struct CustomPump
{
    void* Receive( ) 
    { return new Message{ }; };

    void Send( void* message )
    {
        auto ptr{ static_cast<Message*>( message ) };
        std::cout << "Sending: " << ptr->payload << '\n';
        delete ptr;
    }
};

template<typename MessageType>
class Active
{
public:
    using Callback = void( * )( MessageType* msg );

    Active( MessagePump pump, Callback cb )
        : pump_{ std::move( pump ) },
          cb_{ cb } { }

    void Start( )
    {
        while ( true )
        {
            auto message{ pump_.Receive( ) };
            if ( !message )
            {
                std::cout << "No message\n";
                break;
            }
            else
            {
                auto message{ static_cast<MessageType*>( result ) };
                std::invoke( cb_, message );
                pump_.Send( message );
            } 
        }
    }
private:
    MessagePump pump_;
    Callback cb_;
};


int main ( )
{
    Active<Message> active{ CustomPump{ }, 
        [ ]( Message* msg ){ std::cout << "Received: " << msg->payload << '\n'; } };
    active.Start( );
}

【讨论】:

    猜你喜欢
    • 2020-07-31
    • 2018-09-15
    • 1970-01-01
    • 2019-03-22
    • 2014-11-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-12
    相关资源
    最近更新 更多