Я немного злоупотребляю шаблонами 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)
{
}
Я знаю, что это нехорошо, поскольку нет ничего (на уровне компилятора), чтобы заставить Class1
и Class2
реализовывать один и тот же интерфейс, но по соображениям скорости я нарушаю некоторые правила.
Я бы хотел создать функцию обратного вызова на 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();
}
Теперь я знаю, что вы спрашиваете, зачем использовать Class1
и Class2
? Что ж, основное различие между работой с файлом и работой с памятью настолько велико, что имеет смысл иметь разные классы для разных типов ввода (а не просто перегружать конструктор и заставлять его вести себя по-разному для разных входных данных). Опять же, я проверил это, и гораздо быстрее обрабатывать особые случаи в своих собственных классах, а не иметь case
s / if
s в каждой функции.
Итак, я хотел бы скрыть большую часть этой реализации от младших разработчиков, я не хочу, чтобы им приходилось создавать три разных перегруженных DoStuff
для обработки различных входных данных. В идеале я бы просто настроил некоторый тип обратного вызова с #defines
, и все, что им нужно было бы сделать, это создать класс с именем DoStuff
, перегрузить оператор ()
и заставить функтор работать.
Проблема, с которой я столкнулся, заключается в том, что функция DoStuff
, которая выполняет эту работу, только шаблонизируется <class C>
, а сама C - <class DataType>
, и все, что я не могу понять, как передать все в общий способ. Например, я не могу использовать template <class C<DataType>>
или 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
функцию, а другие люди будут писать функции CallBack.
Есть идеи?
РЕДАКТИРОВАТЬ для разъяснения для Джальфа:
У меня есть два очень похожих класса, 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>
, скрытое там. Компилятор недоволен этим.