删除虚拟呼叫
Remove virtual call
数据以下列形式从给定通道到达:
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...
然后分派给相应的handlers:
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);
}
一切正常。但是我对这个解决方案不满意。我宁愿去掉基数 class,也不保留指针数组(由于高速缓存行)。我确信有更好的方法。最终,我打算允许开发人员 'register' 他们的类型处理程序。谢谢。
我认为使用包装器构建的 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);
使用switch
.
default:
捕获 - 理想情况下,信号 - 不受支持的类型。
这是实现您预期目标的最简单的代码。 "Everyone" 会明白的。
它也几乎在所有其他方面都大放异彩。
与 switch 相比,有什么可以改进的地方?我能想到的是:
引入新的有效负载类型需要引入新的处理函数、该类型的新枚举,并将其添加到 switch 语句中。您可能会忘记在 switch 语句中添加
如果超过 ca。十几个值,很难直观地验证所有枚举和处理程序是否匹配,例如您可能会调用错误的处理程序。
最后一个问题仍然存在于现在发布的所有替代方案中,我想不出一个聪明的模板解决方案。
如果案例较多,我会考虑代码生成。
有文件
A
B
C
可以生成负载类型的枚举、struct
和处理程序函数的(转发)声明,以及带有开关的中央处理程序的实现。
向此文件添加另一种有效负载类型会破坏构建,直到您实现处理程序,然后一切都会恢复原状。
缺点是更复杂的构建过程,以及生成代码的常见问题。
**[edit]* 不要误会我的意思 - 这个问题当然很有趣 - 也许我们可以想出一个漂亮的解决方案,and/sor 从中学习一些东西。这只是我对生产的建议。
数据以下列形式从给定通道到达:
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...
然后分派给相应的handlers:
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);
}
一切正常。但是我对这个解决方案不满意。我宁愿去掉基数 class,也不保留指针数组(由于高速缓存行)。我确信有更好的方法。最终,我打算允许开发人员 'register' 他们的类型处理程序。谢谢。
我认为使用包装器构建的 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);
使用switch
.
default:
捕获 - 理想情况下,信号 - 不受支持的类型。
这是实现您预期目标的最简单的代码。 "Everyone" 会明白的。
它也几乎在所有其他方面都大放异彩。
与 switch 相比,有什么可以改进的地方?我能想到的是:
引入新的有效负载类型需要引入新的处理函数、该类型的新枚举,并将其添加到 switch 语句中。您可能会忘记在 switch 语句中添加
如果超过 ca。十几个值,很难直观地验证所有枚举和处理程序是否匹配,例如您可能会调用错误的处理程序。
最后一个问题仍然存在于现在发布的所有替代方案中,我想不出一个聪明的模板解决方案。
如果案例较多,我会考虑代码生成。
有文件
A
B
C
可以生成负载类型的枚举、struct
和处理程序函数的(转发)声明,以及带有开关的中央处理程序的实现。
向此文件添加另一种有效负载类型会破坏构建,直到您实现处理程序,然后一切都会恢复原状。
缺点是更复杂的构建过程,以及生成代码的常见问题。
**[edit]* 不要误会我的意思 - 这个问题当然很有趣 - 也许我们可以想出一个漂亮的解决方案,and/sor 从中学习一些东西。这只是我对生产的建议。