虚拟基类的模板化编译时版本可以作为接口吗?

问题描述 投票:0回答:1

我正在开发基于模型的系统工程(MBSE)的框架。在此,我有一个名为 Bridge 的构造。桥接器可以从桥接器的一端向另一端发送信号(异步调用)和/或操作(同步调用)。信号和操作也可能有有效负载,由框架的最终用户(另一个 C++ 程序员)定义。

桥接器已联网,因此桥接器的输入通过网络套接字连接到桥接器的输出。为此,信号和/或操作(简称 SigOps)以及可能的有效负载需要序列化。

网桥有一个传出队列,用于缓冲并保留发出的 SigOp 序列。因此,该队列使用通用 SigOp 指针来存储 SigOp。但是,仍然需要使用 SigOp 类型的特定序列化器。

通常的方法是使用定义接口的虚拟基类,然后让派生类添加实现。类似的东西:

// A Bridge-type specific SigOp virtual base class/interface
template <class tBridgeDeclType>
class SigOpT
   {
   public:
      virtual void initSerialization(SigOpSerDes &fSerializer, SerializationBuffer &fSerializationBuffer) = 0;
      virtual void serializePayLoad(SigOpSerDes &fSigOpSerializer) = 0;
   };

template <class tBridgeDeclType>
class MySigOp : public  SigOpT<tBridgeDeclType>
   {
    void initSerialization(SigOpSerDes &fSerializer, SerializationBuffer &fSerializationBuffer) override final
        {
        // My implementation
        ...
        }
    
    void serializePayLoad(SigOpSerDes &fSigOpSerializer) overried final
        {
        // My serialization code specific for MySigOp<tBridgeDeclType>
        ...
        }
   };

对于问题: 有没有办法在编译时做同样的事情,从而消除虚拟函数、虚函数表等?

CRTP(https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern)不起作用,因为这将删除可以存储在 Bridge 队列中的通用基类/接口类型。

template <tDerived>
class Base
   {
   }

class Derived : Base<Derived>
   {
   }

我还研究了 std::variant 作为在队列中存储不同 SigOp 类型的方法,但我目前锁定在 C++-14 中(std::variant 出现在 C++-17 中)。此外,可以存储 Bridge 的所有 SigOp 类型及其可能的有效负载的 std::variant 将占用大量内存(除非我记错了)。

目前,我已经有了这些碎片,但我还没有找到将它们粘合在一起的方法。桥梁模板:

template <class tBridgeDeclType>
class BridgeT
    {

    // Enqueue Signal for subsequent serialization and transportation over network by ZMQ
    template <class tSignalType>
    inline static void post(const tSignalType &fSignal)
        {

        /**
         * Here, I know all types needed to fully define how a serialization shall be done.
         * 
         * Thus:
         * 
         *  tBridgeDeclType - The bridge declaration type. This type stores 
         *     the relations to the types below.
         *  
         *  typename tBridgeDeclType::SigOpTypes - A std::tuple<> listing all SigOp types
         *     supported by the bridge
         *  
         *  tSignalType - The specific type of SigOp (in this case a Signal) that we want
         *     to enqueue in the bridges outgoing SigOp queue (see below).
         *  
         * This setup does, in the background, provides type safety so that only SigOps 
         * explicit supported by the particular bridge type can be posted and transmitted
         * by the bridge. Using unsupported SigOps generate compilation error.  
         */
            
            
        // How to combine this with a SigOp-type specific 
        // SerializableSigOpT<tBridgeDeclType, tSigOpType> still being able to use a
        // Bridge-generic SigOp queue slot type?    

        // Enqueue SigOp
        queue.push(fSignal);
        }
    
    static std::queue<SigOpT<tBridgeDeclType>> queue;
    };

还有一个 SigOp 序列化器模板,可在编译时重定向到正确的序列化代码:

template <class tBridgeDeclType, class tSigOpType>
class SerializableSigOpT : public SigOpT<tBridgeDeclType>
    {
    public:

        inline static void initSerialization(SigOpSerDes &fSigOpSerializer, const SigOpSeqNumType fSeqNumber, SerializationBuffer &fSerBuffer)
            {
            fSigOpSerializer.initSerialization(getSigOpTypeIdT<typename tBridgeDeclType::SigOpTypes, tSigOpType>::cId, fSeqNumber, fSerBuffer);
            }

        inline static void serializePayLoad(const tSigOpType &fSigOp, SigOpSerDes &fSigOpSerializer)
            {

            // Serialize pay load when such exists. This is a conditional template that compiles to no code when SigOp has no pay load.
            PayLoadSerDesT<tBridgeDeclType, tSigOpType, tSigOpType::hasPayLoad()>::serializePayLoad(fSigOp, fSigOpSerializer);
            }
    };

是否可以使用 C++ 模板来做到这一点,或者它是那些表面上看起来很简单但当你查看细节时魔鬼就会出现的事情之一???

/尼尔斯

c++ templates crtp
1个回答
0
投票

您的目标是消除虚拟函数和 vtable,同时保持基于模型的系统工程 (MBSE) 框架的类型安全。虽然在不引入一些复杂性的情况下实现这一点具有挑战性,但使用 C++ 模板可以做到这一点。但是,请记住,这种方法需要仔细设计,并且可能涉及一些高级模板元编程技术。我将提供潜在解决方案的高级概述。

您可以使用编译时类型信息来根据特定的 SigOp 类型调度序列化和其他操作。为了实现这一点,您可以利用类型特征、模板专业化和静态多态性。

使用类型列表或元组来维护 Bridge 中支持的 SigOp 类型列表。该列表对于在编译时检查兼容性至关重要。

您可以创建一组标签类型来标识特定的 SigOp 类型,并使用这些标签来选择适当的序列化和处理逻辑。

以下是如何构建代码的简化示例:

template <typename... SigOpTypes>
class Bridge {
public:
    template <typename SigOpType>
    void post(const SigOpType& sigOp) {
        // Ensure that SigOpType is supported by this Bridge
        static_assert(Contains<SigOpType, SigOpTypes...>::value, "Unsupported SigOpType");
        
        // Dispatch to the appropriate serialization logic based on SigOpType
        SerializeSigOp(sigOp);
        
        // Enqueue the SigOp for transportation
        queue.push(sigOp);
    }

private:
    std::queue<SigOpBase*> queue;

    template <typename SigOpType>
    void SerializeSigOp(const SigOpType& sigOp) {
        // Implement SigOpType-specific serialization logic here
        // You can use tag dispatch or function overloading.
    }
};

在这里,SigOpBase 将是 SigOp 类型的公共基类,允许它们存储在同一个队列中。

关键是使用类型特征和标签分派来选择适当的序列化逻辑并在编译时检查支持的 SigOp 类型。您可以单独实现每个 SigOp 类型的序列化逻辑,无需虚拟函数。

这种方法提供了类型安全性并避免了虚拟函数和虚函数表的开销,但它可能需要一些仔细的设计和额外的复杂性,特别是如果您有许多受支持的 SigOp 类型。您还需要分别为每个 SigOp 类型实现各种序列化逻辑。

© www.soinside.com 2019 - 2024. All rights reserved.