1

我有点滥用 C++ 模板,但我无法弄清楚一些事情。假设我有两种确实应该从基本类型继承的类型,但是出于速度原因,我无法承受虚函数开销(我已经对其进行了基准测试,而虚调用毁了我的一切!)。

首先,这是我的两个课程

template<class DataType> class Class1
{
    //Lots of stuff here
}

template<Class DataType> class Class2
{
    //The same stuff as in Class1, but implemented differently
}

在典型的 oo 设计中,Class1并且Class2会继承自IInterface并且我可以有一个看起来像这样的函数

DoStuff(IInterface& MyInterface)
{
}

但是我做不到,所以我做了这个

template <class C>
DoStuff(C& c)
{
}

我知道这并不漂亮,因为(在编译器级别)没有任何东西可以强制执行Class1Class2实现相同的接口,但是出于速度原因,我违反了一些规则。

我想做的是在 上创建一个回调函数DoStuff,但我不知道如何使它与模板一起工作(特别是因为那里隐藏着。

例如,这现在有效

DoStuff(char* filename)
{
    switch (//figure out the type i need to make)
    {
    case 1: return DoStuff(Class1<int>(filename));
    case 2: return DoStuff(Class1<double>(filename));
    }
}

template<class DataType>
DoStuff(DataType* pdata)
{
    return DoStuff(Class2<DataType>(pdata));
}

template<class C>
DoStuff(C c)
{
  c.Print();
}

现在我知道你在问,为什么要使用Class1and Class2?好吧,处理文件和处理内存之间的根本区别是如此之大,以至于为不同类型的输入使用不同的类是有意义的(而不是仅仅重载构造函数并让它对不同的输入表现不同)。同样,我确实对此进行了基准测试,在他们自己的类中处理特殊情况比在每个函数中都有cases/ s快得多。if

所以我想做的是对初级开发人员隐藏很多这种实现,我不希望他们必须创建三个不同的重载DoStuffs 来处理不同的输入。理想情况下,我只需设置某种类型的回调,#defines他们需要做的就是创建一个名为的类并重DoStuff载运()算符并让函子完成工作。

我遇到的麻烦是,DoStuff完成这项工作的函数只是模板化,<class C>但 C 本身是模板化的<class DataType>,我无法弄清楚如何以通用方式传递所有内容。例如,我不能使用template <class C<DataType>>or template<template< class DataType> class C>。它只是不会编译。

有没有人有一个很好的技巧来使用这个嵌套的模板类进行通用回调,无论是函数还是仿函数(我不在乎)?基本上我想要一些东西,我可以编写一个不关心存储数据的类的通用函数,并让一个最常见的函数调用它,以确定要使用哪个类。

BigSwitch(CallBack,Inputs)
{
    switch(//something)
    {
    case 1: return CallBack(Class1<Type>(Inputs))
    case 2: return CallBack(Class2<Type>(Inputs))
    }
}

这样我可以编写一个BigSwitch函数并让其他人编写回调函数。

有任何想法吗?


编辑以澄清 Jalf:

我有两个非常相似的类,Class1它们Class2代表基本相同类型的数据,但是数据存储有很大不同。为了使其更具体,我将使用一个简单的示例:Class1是一个简单的数组,Class2看起来像一个数组,但不是存储在内存中而是存储在文件中(因为它太大而无法放入内存)。所以我现在就给他们MemArray打电话FileArray。所以假设我想要数组的总和。我可以做这样的事情

template <class ArrayType, class ReturnType>
ReturnType Sum(ArrayType A)
{
    ReturnType S=0;
    for (int i=A.begin();i<A.end();++i)
    {
      S+=A[i];
    }
    return S;
}

但是现在,我需要一种将真实数据加载到数组中的方法。如果它是一个基于内存的数组,我会这样做

MemArray<DataType> M(pData);

如果它是文件格式,我会这样做

FileArray<DataType> F(filename);

并且这两个调用都是有效的(因为编译器在编译时生成两个代码路径)

double MS=Sum<MemArray<DataType>,double>(M);
double FS=Sum<FileArray<DataType>,double>(F);

所有这些都假设我知道 DataType 是什么,但是对于基于文件的数组,我可能不知道数据类型,直到我打开文件并查询标题以了解数组中的数据类型。

double GetSum(char* filename)
{
    int DataTypeCode=GetDataTypeCode(filename);
    switch (DataTypeCode)
    {
    case 1: return Sum<FileArray<int>,double>(FileArray<int>(filename));
    case 2: return Sum<FileArray<double>,double>(FileArray<double>(filename));
    }
}
template <class DataType>
double GetSum(DataType* pData)
{
    return Sum<MemArray<DataType>,double>(MemArray<DataType>(pData));
}

所有这些都有效,但它需要编写两个重载GetX函数和一个X函数来完成我想做的所有事情。除了它调用的GetX函数之外,这些函数每次基本上都是相同的代码。X所以我希望能够写出类似的东西

double GetX(CallBackType X, char* filename)
{
    int DataTypeCode=GetDataTypeCode(filename);
    switch (DataTypeCode)
    {
    case 1: return X<FileArray<int>,double>(FileArray<int>(filename));
    case 2: return X<FileArray<double>,double>(FileArray<double>(filename));
    }
}
template <class DataType>
double GetX(CallBackType, DataType* pData)
{
    return X<MemArray<DataType>,double>(MemArray<DataType>(pData));
}

这样我就可以打电话

GetX(Sum,filename)

然后当其他人想要添加一个新函数时,他们需要做的就是编写函数并调用

GetX(NewFunction,filename)

我只是在寻找一种方法来编写我的重载GetX函数和我的X函数,以便我可以从实际算法中抽象出输入/存储的方式。通常,这不是一个难题,只是我遇到了麻烦,因为该X函数包含一个模板参数,该参数本身是模板化的。template<class ArrayType>也有一个隐含的隐藏ArrayType<DataType>在那里。编译器对此不满意。

4

2 回答 2

3

关注问题的最初部分(为什么不只是使用继承):

进行编译时多态性并通过基类访问派生类成员的常用方法是通过CRTP模式。

template <typename T>
class IInterface {
  void DoStuff() {
    void static_cast<T*>(this)->DoStuff()
  }
};

class Class1 : IInterface<Class1> {
  void DoStuff(){...}
}

那能解决你的问题吗?

编辑: 顺便说一句,我很高兴我能提供帮助,但下次请尝试更多地构建你的问题。

我真的不知道你在问什么,所以这只是在黑暗中刺伤,基于你问题的前 3 行。;)

您永远不会真正解释您要实现的目标,而只会解释您无法正常工作的解决方法是什么样的。开始陈述问题,因为这是我们真正需要知道的。然后,您可以提供有关当前解决方法的详细信息。在发布代码时,添加一些上下文。DoStuff() 从哪里调用,为什么初级开发人员需要定义它们?(你已经这样做了,不是吗?)

初级开发人员首先会使用此代码做什么?

令人困惑的是,您提供了具体情况(1 和 2),而不是 switch 语句本身(//某事)

如果您尝试使回答的人更容易,下次您将获得更多(更好和更快)的答案。:)

于 2009-05-13T21:46:22.337 回答
0

至于您关于“通用回调”的问题,您可以使用boost::function但它本质上是在幕后使用虚函数(它可能不是 - 但至少是一个类似的概念),所以您正在寻找的性能差异不会在那里(实际上 boost::function 可能会因为堆分配而变慢)。

于 2009-05-13T21:36:12.137 回答