避免打开模板参数

Avoid switching on template parameters

本文关键字:参数      更新时间:2023-10-16

简化后的类层次结构如下:

class BaseVec {
  public:
    BaseVec() {};
    virtual ~BaseVec() {};
    virtual double get_double(int i) const = 0;
};
template<typename T>
class Vec : public BaseVec {
  public:
    Vec() { ... };
    ~Vec() { ... };
    T get(int i) const { ... };
    double get_double(int i) const {
      return get(i);
    };
};

在我的项目中,我反复使用以下形式的代码:

template<typename T>
double foo_template(void* p) {
  Vec<T>* v = reinterpret_cast<Vec<T>*>(p);
  // use v which involves calling get
  return result;
}
double foo(void* p, int type) {
  if (type == 0) {
    return foo_template<double>(p);
  } else if (type == 1) {
    return foo_template<int>(p);
  } else if (...) {
    // etc.
  } else {
    //unsupported type
  }
}

(我可以使用开关并使用枚举,或者先将p转换为BaseVec,然后执行dynamic_cast,但逻辑保持不变)

这不是理想的维护方式。例如,当我添加一个我想要支持的额外类时,我必须在每个if-else-if块中添加一个子句。

简化这一点的一种可能方式是将p转换为BaseVec*并使用get_double方法。但是,由于经常调用此方法,因此性能较差。此外,这并不总是可能的:有时我想调用get方法,因为返回的类型很重要。

我尝试了访问者模式,尽管这有一些优点,但这仍然意味着我必须为每个可能的模板参数编写一段单独的代码。

有没有什么方法可以让代码更容易维护?

附言:我对foo中的内容没有(太多)控制权。foo被外部程序调用(确切地说是R)。因此,我只能将通用指针、int、doubles和字符向量传递给foo

PPS:也欢迎对更好的标题提出建议。

首先,在转换到多态类的指针时不要使用reinterpret_cast。您可以编写一个简单的指针包装器,它允许您使用安全强制转换运算符static_cast:

template <class Type>
class PointerWrapper
{
public:
    PointerWrapper(Type* object);
    PointerWrapper& operator=(Type* object);
    Type* operator->();
protected:
    Type* object;
};
template <class Type>
PointerWrapper<Type>::PointerWrapper(Type* object) :
    object(object)
{
}
template <class Type>
PointerWrapper<Type>& PointerWrapper<Type>::operator=(Type* object)
{
    this->object = object;
}
template <class Type>
Type* PointerWrapper<Type>::operator->()
{
    return object;
}

现在你可以写:

typedef PointerWrapper<BaseVec> BaseVecPointer;
template<typename T>
double foo(void* p) {
    BaseVecPointer* vp = static_cast<BaseVecPointer*>(p);
    // ...
    // ... = (*vp)->get_double(...);
    // ...
    return result;
}

在该代码中使用了多态性功能,即调用函数get_double而不是调用get

但是,如果您只想调用get,而不是get_double,即您想根据运行时变量的值调用具有不同模板参数的模板函数,则可以使用以下方法:

enum FooTypes
{
    NoFooType = -1,
    DoubleFooType = 0,
    IntegerFooType = 1,
    // ...
    FooTypesCount
};
template<FooTypes fooType>
struct ChooseType
{
    static
    const FooTypes value = NoFooType;
    typedef void Type;
};
template<>
struct ChooseType<DoubleFooType>
{
    static
    const FooTypes value = DoubleFooType;
    typedef double Type;
};
template<>
struct ChooseType<IntegerFooType>
{
    static
    const FooTypes value = IntegerFooType;
    typedef int Type;
};

在这里,您应该为type变量的所有可能值编写类模板ChooseType的专门化。以下代码描述了函数ChooseFoo,它选择了foo_template函数模板应该调用的专业化:

typedef double (*FooFunction)(void*);
template<FooTypes fooType>
FooFunction ChooseFooImpl(int type)
{
    if (type == fooType)
    {
        if (ChooseType<fooType>::value != NoFooType)
        {
            return foo_template<typename ChooseType<fooType>::Type>;
        }
        else
        {
            return NULL;
        }
    }
    else
    {
        return ChooseFooImpl<(FooTypes)(fooType - 1)>(type);
    }
}
template<>
FooFunction ChooseFooImpl<NoFooType>(int type)
{
    return NULL;
}
FooFunction ChooseFoo(int type)
{
    return ChooseFooImpl<FooTypesCount>(type);
}

这就是foo的功能实现:

double foo(void* p, int type)
{
    FooFunction fooFunction = ChooseFoo(type);
    if (fooFunction != NULL)
    {
        return fooFunction(p);
    }
    else
    {
        //unsupported type
        // ...
    }
}

为什么不将foo_templat e更改为:

template<typename T>
double foo_template(Vec<T>*) {
  // use v which involves calling get
  return result;
}

CCD_ 23为

template<typename T>
double foo (Vec<T>* v )
{
return foo_template(v)
}

让论证推理来完成工作?

(你可能可以去掉其中一个函数,但我想保持与原始函数的接近)

C++中的自动调度通过virtuala函数实现运行时多态性,通过static_Cast的mnas实现static_type多态性,但您需要知道要转换什么类型。

使用不同的设计,避免void*,您可以执行以下操作:

template<class Derived>
class static_polimorphic {};
template<class A>
A& upcast(static_polymorphic<A>& sa)
{ return static_cast<A&>(sa); }
template<class A>
const A& upcast(const static_polymorphic<A>& sa)
{ return static_cast<const A&>(sa); }

现在,你们的课程应该像一样

class C1: public static_polymorphic<C1>
{
 ....
};
class C2: public static_polymorphic<C2>
{
 ....
};

多态性将作为应用

template<class A> 
void function(const static_polymorphic<A>& sa)
{
   A& a = upcast(sa);
   a.methods();
   ...
}

换句话说,类型不再表示为基成员变量,而是由基模板参数表示。

还要注意的是,作为派生类型所区分的基础,公共函数将不会被视为虚拟的。您可以完全避免基于运行时的多态性,除非您必须将不同的运行时类型创建的对象存储到同一个容器或集合中。

为此,您可以使用第二个具有抽象虚拟函数的非临时基作为派生类中的"启动器"。(最好使用运行时多态性作为第一个基础,以简化运行时指针转换,因为不会有偏移)