CRTP的运行时多态性设计和策略

Run-time polymorphism design and strategies with CRTP

本文关键字:策略 多态性 运行时 CRTP      更新时间:2023-10-16

在我的工作中,我有很多带有许多内部函数调用的循环;性能在这里是至关重要的,虚拟函数调用的开销是不可接受的,所以我试图通过使用CRTP来避免动态多态性,比如:

template<class DType>
struct BType {
DType& impl(){ return *static_cast<DType*>(this); }
void Func(){ impl().Func(); }
};
struct MyType : public BType<MyType> {
void Func(){ /* do work */ }
};
template<class DType>
void WorkLoop(BType<DType>* func){
for  (int i=0;i<ni;++i){ func->func(); }
}
struct Worker {
void DoWork(){ WorkLoop(&thing) };
private:
MyType thing;
};
Worker worker;
worker.DoWork();

旁白:实际使用CRTP类的正确方法是什么现在我需要实际类型依赖于运行时用户选项,通常带有抽象基类/策略模式的动态多态性是正确的设计,但我负担不起虚拟函数调用。实现这一点的一种方法似乎是进行一些分支:

struct Worker {
void DoWork(){
if (option=="optionA"){
TypeA thing;
WorkLoop(thing); }
else if (option=="optionB"){
TypeB thing;
WorkLoop(thing); } 
...

但这似乎是一个糟糕的设计。在这里将其作为模板参数传递(或使用基于策略的设计)似乎是一种选择:

template<class T>
struct Worker {
void DoWork(){ WorkLoop(&thing) };
T thing;
};
if (option=="optionA"){
Worker<TypeA> worker; worker.DoWork() } ...

但这里的worker只在if分支中有作用域,我需要它有一个程序长度的生命周期。此外,相关的用户选项可能会指定4+个"策略",每个策略都有几个选项(比如4个),所以看起来你很快就会遇到一个严重的问题,一个模板化的类可能会采用4*4*4*4个模板组合中的一个。

此外,将循环逻辑移动到类型中不是一个选项——如果是这样的话,虚拟函数调用开销将可以忽略不计,我会使用正常的多态性。循环的实际控制可能相当复杂,并且在运行时会有所不同。

这是否意味着我应该尝试构建一个自定义迭代器,并将其作为函数参数传递,并使用正常的多态性,或者这会产生类似的开销

在运行时选择类而不使用指向抽象基类的指针的好设计是什么

您有一个典型的运行时编译时间调度问题:"此外,相关的用户选项可能会指定额外的策略,每个策略都有几个选项"。您的代码必须支持许多在编译时不知道的选项组合。

这意味着您必须为每个可能的组合编写一些代码,然后将用户的选择分派到其中一个组合上。这意味着您必须有一些丑陋且效率不高的代码,在这些代码中,您可以解析用户的运行时决策,并将它们分派到预定义的模板上。

为了保持尽可能高的效率,您希望在非常高的级别,尽可能靠近入口点进行此调度。另一方面,您的低级代码可以随意模板化。

这意味着分派可以有几个步骤,从非模板代码到混合模板和选项,再到完全临时化。

通常,标签和策略(而不是CRTP)可以更好地实现这一点,但这在很大程度上取决于您的算法和选项。