删除虚拟呼叫

时间:2015-02-12 16:40:18

标签: c++ templates c++11 template-meta-programming

数据以下列形式从给定频道到达:

void DispatchIncomingChannelData(uint8_t const typeId, 
                                 void * payload, 
                                 uint32_t const payloadSize);

有效载荷可能有以下几种类型:

struct PayloadA { /* Various Data */ };
struct PayloadB { /* Various Data */ };
struct PayloadC { /* Various Data */ };
// Other PODs... 

然后发送给相应的处理程序:

void ProcessPayload_A(PayloadA * payload) { /* PayloadA code */ }
void ProcessPayload_B(PayloadB * payload) { /* PayloadB code */ }
void ProcessPayload_C(PayloadC * payload) { /* PayloadC code */ }

没有模板,人们可以简单地提供一个开关,然后扔掉!有了模板,我开始如下:

struct BasePayloadProcessor abstract
{
public:
    virtual void ProcessPayload(void * const payload, uint32_t const payloadSize) = 0;
};

// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

template<typename T>
struct TypedPayloadProcessor : BasePayloadProcessor
{
public:
    using PayloadHandler = void (*)(T * message);

    TypedPayloadProcessor(uint8_t const typeId, PayloadHandler payloadHandler) :
        _payloadHandler { payloadHandler },
        _type { typeid(T) },
        _typeId { typeId }
        { }

    virtual void ProcessPayload(void * const payload, uint32_t const payloadSize) override
    {
        ASSERT(_payloadHandler);
        ASSERT(payloadSize == sizeof(T));
        T * t = reinterpret_cast<T *>(payload);
        _payloadHandler(t);
    }

private:
    PayloadHandler _payloadHandler;
    type_info const & _type;         // <-- These two members are 
    uint8_t const _typeId;           // <-- not really necessary
};

为了测试,定义了一个枚举,一系列处理器,并对它们进行了硬编码。

enum class PayloadTypes : uint8_t
{
    Invalid = 0x00,
    A = 0x01,
    B = 0x02,
    C = 0x03,
    // etc...
    TotalTypes = static_cast<uint8_t>(PayloadTypes::C) + 0x01
};

// array of base class pointers
std::array<BasePayloadProcessor *, static_cast<uint8_t>(PayloadTypes::TotalTypes)> payloadProcessors { };

// populate the array
payloadProcessors[static_cast<uint8_t>(PayloadTypes::Invalid)] = nullptr;
payloadProcessors[static_cast<uint8_t>(PayloadTypes::A)] = new TypedPayloadProcessor<PayloadA>(static_cast<uint8_t>(PayloadTypes::A), ProcessPayload_A);
payloadProcessors[static_cast<uint8_t>(PayloadTypes::B)] = new TypedPayloadProcessor<PayloadB>(static_cast<uint8_t>(PayloadTypes::B), ProcessPayload_B);
payloadProcessors[static_cast<uint8_t>(PayloadTypes::C)] = new TypedPayloadProcessor<PayloadC>(static_cast<uint8_t>(PayloadTypes::B), ProcessPayload_C);
// etc....

实施调度功能如下:

void DispatchIncomingChannelData(uint8_t const typeId, 
                                 void * payload, 
                                 uint32_t const payloadSize)
{
    ASSERT(typeId > static_cast<uint8_t>(PayloadTypes::Invalid));
    ASSERT(typeId < static_cast<uint8_t>(PayloadTypes::TotalTypes));
    payloadProcessors[typeId]->ProcessPayload(payload, payloadSize);
}

一切正常。但是我对这个解决方案不满意。我更希望摆脱基类,而不是保留一个指针数组(由于缓存行)。我确信有更好的方法。最后,我打算允许开发人员“注册”他们的类型处理程序。感谢。

2 个答案:

答案 0 :(得分:2)

我认为使用包装器构建function数组会更容易:

using CallbackFn = std::function<void(void*, const uint32_t)>;

template <typename T>
CallbackFn make_processor(void (*func)(T*))
{
    return [=](void* payload, const uint32_t size){);
        ASSERT(payloadSize == sizeof(T));
        func(static_cast<T*>(payload));
    };
}

这样,您可以直接传递已有的处理功能,并使一切正常工作:

std::array<CallbackFn, 
    static_cast<uint8_t>(PayloadTypes::TotalTypes)> payloadProcessors;

payloadProcessors[static_cast<uint8_t>(PayloadTypes::A)] = 
    make_processor(ProcessPayload_A);
payloadProcessors[static_cast<uint8_t>(PayloadTypes::B)] =
    make_processor(ProcessPayload_B);
payloadProcessors[static_cast<uint8_t>(PayloadTypes::C)] = 
    make_processor(ProcessPayload_C);

答案 1 :(得分:1)

使用switch
default:捕获 - 理想情况下,信号 - 不支持的类型 这是实现您期望目标的最简单的代码。 “每个人”都会理解它。

它几乎涉及所有其他方面。


与交换机相比,有什么可以改进的?我能想到的是:

  • 引入新的有效负载类型需要引入新的处理函数,该类型的新枚举,并将其添加到switch语句中。您可能忘记添加到switch语句

  • 如果超过ca.十几个值,很难直观地验证所有枚举和处理程序是否匹配,例如你可能会调用错误的处理程序。

现在发布的所有备选方案都存在最后一个问题,我无法想到一个聪明的模板解决方案。

如果案例数量较多,我会考虑代码生成

有文件

A
B
C

可以生成有效负载类型的枚举,struct和处理函数的(前向)声明,以及带有交换机的中央处理程序的实现。

在此文件中添加另一个有效内容类型会破坏构建,直到您实现处理程序,然后一切都将再次到位。

下行是一个更复杂的构建过程,也是生成代码的常见问题。

** [编辑] *不要误解我的意思 - 问题肯定很有趣 - 也许我们可以提出一个漂亮的解决方案,并从中学到一些东西。这只是我为生产提供的建议。